fix(compiler): various squashed fixes for the new ngc

introduce the option `allowEmptyCodegenFiles` to generate all generated files,
even if they are empty.
- also provides the original source files from which the file was generated
  in the write file callback
- needed e.g. for G3 when copying over pinto mod names from the original component
  to all generated files

use `importAs` from flat modules when writing summaries
- i.e. prevents incorrect entries like @angular/common/common in the .ngsummary.json files.

change interaction between ng and ts to prevent race conditions
- before Angular would rely on TS to first read the file for which we generate files,
  and then the generated files. However, this can break easily when we reuse an old program.

don’t generate files for sources that are outside of `rootDir`
(see #19337)
This commit is contained in:
Tobias Bosch 2017-09-21 18:05:07 -07:00 committed by Victor Berchet
parent 13613d4acb
commit a8a9660112
16 changed files with 746 additions and 459 deletions

View File

@ -67,6 +67,7 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
return dict(tsc_wrapped_tsconfig(ctx, files, srcs, **kwargs), **{ return dict(tsc_wrapped_tsconfig(ctx, files, srcs, **kwargs), **{
"angularCompilerOptions": { "angularCompilerOptions": {
"generateCodeForLibraries": False, "generateCodeForLibraries": False,
"allowEmptyCodegenFiles": True,
# FIXME: wrong place to de-dupe # FIXME: wrong place to de-dupe
"expectedOut": depset([o.path for o in expected_outs]).to_list() "expectedOut": depset([o.path for o in expected_outs]).to_list()
} }

View File

@ -40,24 +40,21 @@ function runOneBuild(args: string[], inputs?: {[path: string]: string}): boolean
if (args[0] === '-p') args.shift(); if (args[0] === '-p') args.shift();
// Strip leading at-signs, used to indicate a params file // Strip leading at-signs, used to indicate a params file
const project = args[0].replace(/^@+/, ''); const project = args[0].replace(/^@+/, '');
let fileLoader: FileLoader;
if (inputs) {
fileLoader = new CachedFileLoader(fileCache, ALLOW_NON_HERMETIC_READS);
// Resolve the inputs to absolute paths to match TypeScript internals
const resolvedInputs: {[path: string]: string} = {};
for (const key of Object.keys(inputs)) {
resolvedInputs[path.resolve(key)] = inputs[key];
}
fileCache.updateCache(resolvedInputs);
} else {
fileLoader = new UncachedFileLoader();
}
const [{options: tsOptions, bazelOpts, files, config}] = parseTsconfig(project); const [{options: tsOptions, bazelOpts, files, config}] = parseTsconfig(project);
const expectedOuts = config['angularCompilerOptions']['expectedOut']; const expectedOuts = config['angularCompilerOptions']['expectedOut'];
const {basePath} = ng.calcProjectFileAndBasePath(project); const {basePath} = ng.calcProjectFileAndBasePath(project);
const compilerOpts = ng.createNgCompilerOptions(basePath, config, tsOptions); const compilerOpts = ng.createNgCompilerOptions(basePath, config, tsOptions);
const {diagnostics} = compile({fileLoader, compilerOpts, bazelOpts, files, expectedOuts}); const tsHost = ts.createCompilerHost(compilerOpts, true);
const {diagnostics} = compile({
allowNonHermeticReads: ALLOW_NON_HERMETIC_READS,
compilerOpts,
tsHost,
bazelOpts,
files,
inputs,
expectedOuts
});
return diagnostics.every(d => d.category !== ts.DiagnosticCategory.Error); return diagnostics.every(d => d.category !== ts.DiagnosticCategory.Error);
} }
@ -71,14 +68,28 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string
return filePath; return filePath;
} }
export function compile( export function compile({allowNonHermeticReads, compilerOpts, tsHost, bazelOpts, files, inputs,
{fileLoader, compilerOpts, bazelOpts, files, expectedOuts, gatherDiagnostics}: { expectedOuts, gatherDiagnostics}: {
fileLoader: FileLoader, allowNonHermeticReads: boolean,
compilerOpts: ng.CompilerOptions, compilerOpts: ng.CompilerOptions,
tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
bazelOpts: BazelOptions, bazelOpts: BazelOptions,
files: string[], files: string[],
expectedOuts: string[], gatherDiagnostics?: (program: ng.Program) => ng.Diagnostics expectedOuts: string[], gatherDiagnostics?: (program: ng.Program) => ng.Diagnostics
}): {diagnostics: ng.Diagnostics, program: ng.Program} { }): {diagnostics: ng.Diagnostics, program: ng.Program} {
let fileLoader: FileLoader;
if (inputs) {
fileLoader = new CachedFileLoader(fileCache, ALLOW_NON_HERMETIC_READS);
// Resolve the inputs to absolute paths to match TypeScript internals
const resolvedInputs: {[path: string]: string} = {};
for (const key of Object.keys(inputs)) {
resolvedInputs[path.resolve(key)] = inputs[key];
}
fileCache.updateCache(resolvedInputs);
} else {
fileLoader = new UncachedFileLoader();
}
if (!bazelOpts.es5Mode) { if (!bazelOpts.es5Mode) {
compilerOpts.annotateForClosureCompiler = true; compilerOpts.annotateForClosureCompiler = true;
compilerOpts.annotationsAs = 'static fields'; compilerOpts.annotationsAs = 'static fields';
@ -89,7 +100,6 @@ export function compile(
} }
const writtenExpectedOuts = [...expectedOuts]; const writtenExpectedOuts = [...expectedOuts];
const tsHost = ts.createCompilerHost(compilerOpts, true);
const originalWriteFile = tsHost.writeFile.bind(tsHost); const originalWriteFile = tsHost.writeFile.bind(tsHost);
tsHost.writeFile = tsHost.writeFile =

View File

@ -39,7 +39,7 @@ export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext>
abstract resourceNameToFileName(m: string, containingFile: string): string|null; abstract resourceNameToFileName(m: string, containingFile: string): string|null;
abstract fileNameToModuleName(importedFile: string, containingFile: string): string|null; abstract fileNameToModuleName(importedFile: string, containingFile: string): string;
abstract toSummaryFileName(fileName: string, referringSrcFileName: string): string; abstract toSummaryFileName(fileName: string, referringSrcFileName: string): string;
@ -47,6 +47,23 @@ export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext>
abstract getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined; abstract getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined;
protected getImportAs(fileName: string): string|undefined {
// Note: `importAs` can only be in .metadata.json files
// So it is enough to call this.readMetadata, and we get the
// benefit that this is cached.
if (DTS.test(fileName)) {
const metadatas = this.readMetadata(fileName);
if (metadatas) {
for (const metadata of metadatas) {
if (metadata.importAs) {
return metadata.importAs;
}
}
}
}
return undefined;
}
getMetadataFor(filePath: string): ModuleMetadata[]|undefined { getMetadataFor(filePath: string): ModuleMetadata[]|undefined {
if (!this.context.fileExists(filePath)) { if (!this.context.fileExists(filePath)) {
// If the file doesn't exists then we cannot return metadata for the file. // If the file doesn't exists then we cannot return metadata for the file.
@ -56,29 +73,34 @@ export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext>
} }
if (DTS.test(filePath)) { if (DTS.test(filePath)) {
const metadataPath = filePath.replace(DTS, '.metadata.json'); let metadatas = this.readMetadata(filePath);
if (this.context.fileExists(metadataPath)) { if (!metadatas) {
return this.readMetadata(metadataPath, filePath);
} else {
// If there is a .d.ts file but no metadata file we need to produce a // If there is a .d.ts file but no metadata file we need to produce a
// v3 metadata from the .d.ts file as v3 includes the exports we need // v3 metadata from the .d.ts file as v3 includes the exports we need
// to resolve symbols. // to resolve symbols.
return [this.upgradeVersion1Metadata( metadatas = [this.upgradeVersion1Metadata(
{'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)]; {'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)];
} }
return metadatas;
} }
// Attention: don't cache this, so that e.g. the LanguageService
// can read in changes from source files in the metadata!
const metadata = this.getMetadataForSourceFile(filePath); const metadata = this.getMetadataForSourceFile(filePath);
return metadata ? [metadata] : []; return metadata ? [metadata] : [];
} }
readMetadata(filePath: string, dtsFilePath: string): ModuleMetadata[] { protected readMetadata(dtsFilePath: string): ModuleMetadata[]|undefined {
let metadatas = this.resolverCache.get(filePath); let metadatas = this.resolverCache.get(dtsFilePath);
if (metadatas) { if (metadatas) {
return metadatas; return metadatas;
} }
const metadataPath = dtsFilePath.replace(DTS, '.metadata.json');
if (!this.context.fileExists(metadataPath)) {
return undefined;
}
try { try {
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath)); const metadataOrMetadatas = JSON.parse(this.context.readFile(metadataPath));
const metadatas: ModuleMetadata[] = metadataOrMetadatas ? const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) : (Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
[]; [];
@ -87,10 +109,10 @@ export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext>
if (!v3Metadata && v1Metadata) { if (!v3Metadata && v1Metadata) {
metadatas.push(this.upgradeVersion1Metadata(v1Metadata, dtsFilePath)); metadatas.push(this.upgradeVersion1Metadata(v1Metadata, dtsFilePath));
} }
this.resolverCache.set(filePath, metadatas); this.resolverCache.set(dtsFilePath, metadatas);
return metadatas; return metadatas;
} catch (e) { } catch (e) {
console.error(`Failed to read JSON file ${filePath}`); console.error(`Failed to read JSON file ${metadataPath}`);
throw e; throw e;
} }
} }
@ -347,6 +369,11 @@ export class CompilerHost extends BaseAotCompilerHost<CompilerHostContext> {
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`. * NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
*/ */
fileNameToModuleName(importedFile: string, containingFile: string): string { fileNameToModuleName(importedFile: string, containingFile: string): string {
const importAs = this.getImportAs(importedFile);
if (importAs) {
return importAs;
}
// If a file does not yet exist (because we compile it later), we still need to // If a file does not yet exist (because we compile it later), we still need to
// assume it exists it so that the `resolve` method works! // assume it exists it so that the `resolve` method works!
if (importedFile !== containingFile && !this.context.fileExists(importedFile)) { if (importedFile !== containingFile && !this.context.fileExists(importedFile)) {

View File

@ -92,6 +92,12 @@ export interface TsEmitArguments {
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; } export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
export interface LibrarySummary {
fileName: string;
text: string;
sourceFile?: ts.SourceFile;
}
export interface Program { export interface Program {
getTsProgram(): ts.Program; getTsProgram(): ts.Program;
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken): ts.Diagnostic[]; getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken): ts.Diagnostic[];
@ -110,7 +116,7 @@ export interface Program {
customTransformers?: CustomTransformers, customTransformers?: CustomTransformers,
emitCallback?: TsEmitCallback emitCallback?: TsEmitCallback
}): ts.EmitResult; }): ts.EmitResult;
getLibrarySummaries(): {fileName: string, content: string}[]; getLibrarySummaries(): LibrarySummary[];
} }
// Wrapper for createProgram. // Wrapper for createProgram.

View File

@ -126,11 +126,13 @@ export class PathMappedCompilerHost extends CompilerHost {
continue; continue;
} }
if (DTS.test(rootedPath)) { if (DTS.test(rootedPath)) {
const metadataPath = rootedPath.replace(DTS, '.metadata.json'); const metadatas = this.readMetadata(rootedPath);
if (this.context.fileExists(metadataPath)) { if (metadatas) {
return this.readMetadata(metadataPath, rootedPath); return metadatas;
} }
} else { } else {
// Attention: don't cache this, so that e.g. the LanguageService
// can read in changes from source files in the metadata!
const metadata = this.getMetadataForSourceFile(rootedPath); const metadata = this.getMetadataForSourceFile(rootedPath);
return metadata ? [metadata] : []; return metadata ? [metadata] : [];
} }

View File

@ -133,6 +133,9 @@ export interface CompilerOptions extends ts.CompilerOptions {
// Whether to remove blank text nodes from compiled templates. It is `true` by default // Whether to remove blank text nodes from compiled templates. It is `true` by default
// in Angular 5 and will be re-visited in Angular 6. // in Angular 5 and will be re-visited in Angular 6.
preserveWhitespaces?: boolean; preserveWhitespaces?: boolean;
/** generate all possible generated files */
allowEmptyCodegenFiles?: boolean;
} }
export interface CompilerHost extends ts.CompilerHost { export interface CompilerHost extends ts.CompilerHost {
@ -203,6 +206,12 @@ export interface TsEmitArguments {
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; } export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
export interface LibrarySummary {
fileName: string;
text: string;
sourceFile?: ts.SourceFile;
}
export interface Program { export interface Program {
/** /**
* Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources. * Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources.
@ -280,8 +289,9 @@ export interface Program {
}): ts.EmitResult; }): ts.EmitResult;
/** /**
* Returns the .ngsummary.json files of libraries that have been compiled * Returns the .d.ts / .ngsummary.json / .ngfactory.d.ts files of libraries that have been emitted
* in this program or previous programs. * in this program or previous programs with paths that emulate the fact that these libraries
* have been compiled before with no outDir.
*/ */
getLibrarySummaries(): {fileName: string, content: string}[]; getLibrarySummaries(): LibrarySummary[];
} }

View File

@ -14,7 +14,7 @@ import {BaseAotCompilerHost} from '../compiler_host';
import {TypeCheckHost} from '../diagnostics/translate_diagnostics'; import {TypeCheckHost} from '../diagnostics/translate_diagnostics';
import {ModuleMetadata} from '../metadata/index'; import {ModuleMetadata} from '../metadata/index';
import {CompilerHost, CompilerOptions} from './api'; import {CompilerHost, CompilerOptions, LibrarySummary} from './api';
import {GENERATED_FILES} from './util'; import {GENERATED_FILES} from './util';
const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/; const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/;
@ -37,6 +37,11 @@ interface GenSourceFile {
emitCtx: EmitterVisitorContext; emitCtx: EmitterVisitorContext;
} }
export interface CodeGenerator {
generateFile(genFileName: string, baseFileName?: string): GeneratedFile;
findGeneratedFileNames(fileName: string): string[];
}
/** /**
* Implements the following hosts based on an api.CompilerHost: * Implements the following hosts based on an api.CompilerHost:
* - ts.CompilerHost to be consumed by a ts.Program * - ts.CompilerHost to be consumed by a ts.Program
@ -48,11 +53,12 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
TypeCheckHost { TypeCheckHost {
private rootDirs: string[]; private rootDirs: string[];
private moduleResolutionCache: ts.ModuleResolutionCache; private moduleResolutionCache: ts.ModuleResolutionCache;
private originalSourceFiles = new Map<string, ts.SourceFile>(); private originalSourceFiles = new Map<string, ts.SourceFile|undefined>();
private originalFileExistsCache = new Map<string, boolean>(); private originalFileExistsCache = new Map<string, boolean>();
private generatedSourceFiles = new Map<string, GenSourceFile>(); private generatedSourceFiles = new Map<string, GenSourceFile>();
private generatedCodeFor = new Set<string>(); private generatedCodeFor = new Map<string, string[]>();
private emitter = new TypeScriptEmitter(); private emitter = new TypeScriptEmitter();
private librarySummaries = new Map<string, LibrarySummary>();
getCancellationToken: () => ts.CancellationToken; getCancellationToken: () => ts.CancellationToken;
getDefaultLibLocation: () => string; getDefaultLibLocation: () => string;
trace: (s: string) => void; trace: (s: string) => void;
@ -61,10 +67,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
constructor( constructor(
private rootFiles: string[], options: CompilerOptions, context: CompilerHost, private rootFiles: string[], options: CompilerOptions, context: CompilerHost,
private metadataProvider: MetadataProvider, private metadataProvider: MetadataProvider, private codeGenerator: CodeGenerator,
private codeGenerator: (fileName: string) => GeneratedFile[], librarySummaries: LibrarySummary[]) {
private summariesFromPreviousCompilations: Map<string, string>) {
super(options, context); super(options, context);
librarySummaries.forEach(summary => this.librarySummaries.set(summary.fileName, summary));
this.moduleResolutionCache = ts.createModuleResolutionCache( this.moduleResolutionCache = ts.createModuleResolutionCache(
this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context)); this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context));
const basePath = this.options.basePath !; const basePath = this.options.basePath !;
@ -168,6 +174,11 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
'fileNameToModuleName from containingFile', containingFile, 'to importedFile', 'fileNameToModuleName from containingFile', containingFile, 'to importedFile',
importedFile); importedFile);
} }
const importAs = this.getImportAs(importedFile);
if (importAs) {
return importAs;
}
// drop extension // drop extension
importedFile = importedFile.replace(EXT, ''); importedFile = importedFile.replace(EXT, '');
const importedFilePackagName = getPackageName(importedFile); const importedFilePackagName = getPackageName(importedFile);
@ -229,15 +240,18 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
private getOriginalSourceFile( private getOriginalSourceFile(
filePath: string, languageVersion?: ts.ScriptTarget, filePath: string, languageVersion?: ts.ScriptTarget,
onError?: ((message: string) => void)|undefined): ts.SourceFile|undefined { onError?: ((message: string) => void)|undefined): ts.SourceFile|null {
let sf = this.originalSourceFiles.get(filePath); // Note: we need the explicit check via `has` as we also cache results
if (sf) { // that were null / undefined.
return sf; if (this.originalSourceFiles.has(filePath)) {
return this.originalSourceFiles.get(filePath) !;
} }
if (!languageVersion) { if (!languageVersion) {
languageVersion = this.options.target || ts.ScriptTarget.Latest; languageVersion = this.options.target || ts.ScriptTarget.Latest;
} }
sf = this.context.getSourceFile(filePath, languageVersion, onError); // Note: This can also return undefined,
// as the TS typings are not correct!
const sf = this.context.getSourceFile(filePath, languageVersion, onError) || null;
this.originalSourceFiles.set(filePath, sf); this.originalSourceFiles.set(filePath, sf);
return sf; return sf;
} }
@ -250,9 +264,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
return this.metadataProvider.getMetadata(sf); return this.metadataProvider.getMetadata(sf);
} }
updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile|null { updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile {
if (!genFile.stmts) { if (!genFile.stmts) {
return null; throw new Error(
`Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileUrl}`);
} }
const oldGenFile = this.generatedSourceFiles.get(genFile.genFileUrl); const oldGenFile = this.generatedSourceFiles.get(genFile.genFileUrl);
if (!oldGenFile) { if (!oldGenFile) {
@ -271,10 +286,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
return this.addGeneratedFile(genFile, newRefs); return this.addGeneratedFile(genFile, newRefs);
} }
private addGeneratedFile(genFile: GeneratedFile, externalReferences: Set<string>): ts.SourceFile private addGeneratedFile(genFile: GeneratedFile, externalReferences: Set<string>): ts.SourceFile {
|null {
if (!genFile.stmts) { if (!genFile.stmts) {
return null; throw new Error(
`Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileUrl}`);
} }
const {sourceText, context} = this.emitter.emitStatementsAndContext( const {sourceText, context} = this.emitter.emitStatementsAndContext(
genFile.srcFileUrl, genFile.genFileUrl, genFile.stmts, /* preamble */ '', genFile.srcFileUrl, genFile.genFileUrl, genFile.stmts, /* preamble */ '',
@ -288,49 +303,95 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
return sf; return sf;
} }
private ensureCodeGeneratedFor(fileName: string): void { shouldGenerateFile(fileName: string): {generate: boolean, baseFileName?: string} {
if (this.generatedCodeFor.has(fileName)) { // TODO(tbosch): allow generating files that are not in the rootDir
return; // See https://github.com/angular/angular/issues/19337
if (this.options.rootDir && !fileName.startsWith(this.options.rootDir)) {
return {generate: false};
} }
this.generatedCodeFor.add(fileName); const genMatch = GENERATED_FILES.exec(fileName);
if (!genMatch) {
const baseNameFromGeneratedFile = this._getBaseNameForGeneratedSourceFile(fileName); return {generate: false};
if (baseNameFromGeneratedFile) {
return this.ensureCodeGeneratedFor(baseNameFromGeneratedFile);
} }
const sf = this.getOriginalSourceFile(fileName, this.options.target || ts.ScriptTarget.Latest); const [, base, genSuffix, suffix] = genMatch;
if (!sf) { if (suffix !== 'ts') {
return; return {generate: false};
}
let baseFileName: string|undefined;
if (genSuffix.indexOf('ngstyle') >= 0) {
// Note: ngstyle files have names like `afile.css.ngstyle.ts`
if (!this.originalFileExists(base)) {
return {generate: false};
}
} else {
// Note: on-the-fly generated files always have a `.ts` suffix,
// but the file from which we generated it can be a `.ts`/ `.d.ts`
// (see options.generateCodeForLibraries).
baseFileName = [`${base}.ts`, `${base}.d.ts`].find(
baseFileName => this.isSourceFile(baseFileName) && this.originalFileExists(baseFileName));
if (!baseFileName) {
return {generate: false};
}
}
return {generate: true, baseFileName};
} }
const genFileNames: string[] = []; shouldGenerateFilesFor(fileName: string) {
if (this.isSourceFile(fileName)) { // TODO(tbosch): allow generating files that are not in the rootDir
// Note: we can't exit early here, // See https://github.com/angular/angular/issues/19337
// as we might need to clear out old changes to `SourceFile.referencedFiles` return !GENERATED_FILES.test(fileName) && this.isSourceFile(fileName) &&
// that were created by a previous run, given an original CompilerHost (!this.options.rootDir || pathStartsWithPrefix(this.options.rootDir, fileName));
// that caches source files.
const genFiles = this.codeGenerator(fileName);
genFiles.forEach(genFile => {
const sf = this.addGeneratedFile(genFile, genFileExternalReferences(genFile));
if (sf) {
genFileNames.push(sf.fileName);
}
});
}
addReferencesToSourceFile(sf, genFileNames);
} }
getSourceFile( getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget, fileName: string, languageVersion: ts.ScriptTarget,
onError?: ((message: string) => void)|undefined): ts.SourceFile { onError?: ((message: string) => void)|undefined): ts.SourceFile {
this.ensureCodeGeneratedFor(fileName); // Note: Don't exit early in this method to make sure
const genFile = this.generatedSourceFiles.get(fileName); // we always have up to date references on the file!
if (genFile) { let genFileNames: string[] = [];
return genFile.sourceFile; let sf = this.getGeneratedFile(fileName);
if (!sf) {
const summary = this.librarySummaries.get(fileName);
if (summary) {
if (!summary.sourceFile) {
summary.sourceFile = ts.createSourceFile(
fileName, summary.text, this.options.target || ts.ScriptTarget.Latest);
}
sf = summary.sourceFile;
genFileNames = [];
}
}
if (!sf) {
sf = this.getOriginalSourceFile(fileName);
const cachedGenFiles = this.generatedCodeFor.get(fileName);
if (cachedGenFiles) {
genFileNames = cachedGenFiles;
} else {
if (!this.options.noResolve && this.shouldGenerateFilesFor(fileName)) {
genFileNames = this.codeGenerator.findGeneratedFileNames(fileName);
}
this.generatedCodeFor.set(fileName, genFileNames);
}
}
if (sf) {
addReferencesToSourceFile(sf, genFileNames);
} }
// TODO(tbosch): TypeScript's typings for getSourceFile are incorrect, // TODO(tbosch): TypeScript's typings for getSourceFile are incorrect,
// as it can very well return undefined. // as it can very well return undefined.
return this.getOriginalSourceFile(fileName, languageVersion, onError) !; return sf !;
}
private getGeneratedFile(fileName: string): ts.SourceFile|null {
const genSrcFile = this.generatedSourceFiles.get(fileName);
if (genSrcFile) {
return genSrcFile.sourceFile;
}
const {generate, baseFileName} = this.shouldGenerateFile(fileName);
if (generate) {
const genFile = this.codeGenerator.generateFile(fileName, baseFileName);
return this.addGeneratedFile(genFile, genFileExternalReferences(genFile));
}
return null;
} }
private originalFileExists(fileName: string): boolean { private originalFileExists(fileName: string): boolean {
@ -344,48 +405,19 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
fileExists(fileName: string): boolean { fileExists(fileName: string): boolean {
fileName = stripNgResourceSuffix(fileName); fileName = stripNgResourceSuffix(fileName);
if (fileName.endsWith('.ngfactory.d.ts')) { if (this.librarySummaries.has(fileName) || this.generatedSourceFiles.has(fileName)) {
// Note: the factories of a previous program
// are not reachable via the regular fileExists
// as they might be in the outDir. So we derive their
// fileExist information based on the .ngsummary.json file.
if (this.summariesFromPreviousCompilations.has(summaryFileName(fileName))) {
return true; return true;
} }
} if (this.shouldGenerateFile(fileName).generate) {
// Note: Don't rely on this.generatedSourceFiles here,
// as it might not have been filled yet.
if (this._getBaseNameForGeneratedSourceFile(fileName)) {
return true; return true;
} }
return this.summariesFromPreviousCompilations.has(fileName) || return this.originalFileExists(fileName);
this.originalFileExists(fileName);
}
private _getBaseNameForGeneratedSourceFile(genFileName: string): string|undefined {
const genMatch = GENERATED_FILES.exec(genFileName);
if (!genMatch) {
return undefined;
}
const [, base, genSuffix, suffix] = genMatch;
if (suffix !== 'ts') {
return undefined;
}
if (genSuffix.indexOf('ngstyle') >= 0) {
// Note: ngstyle files have names like `afile.css.ngstyle.ts`
return base;
} else {
// Note: on-the-fly generated files always have a `.ts` suffix,
// but the file from which we generated it can be a `.ts`/ `.d.ts`
// (see options.generateCodeForLibraries).
return [`${base}.ts`, `${base}.d.ts`].find(
baseFileName => this.isSourceFile(baseFileName) && this.originalFileExists(baseFileName));
}
} }
loadSummary(filePath: string): string|null { loadSummary(filePath: string): string|null {
if (this.summariesFromPreviousCompilations.has(filePath)) { const summary = this.librarySummaries.get(filePath);
return this.summariesFromPreviousCompilations.get(filePath) !; if (summary) {
return summary.text;
} }
return super.loadSummary(filePath); return super.loadSummary(filePath);
} }
@ -393,13 +425,19 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
isSourceFile(filePath: string): boolean { isSourceFile(filePath: string): boolean {
// If we have a summary from a previous compilation, // If we have a summary from a previous compilation,
// treat the file never as a source file. // treat the file never as a source file.
if (this.summariesFromPreviousCompilations.has(summaryFileName(filePath))) { if (this.librarySummaries.has(filePath)) {
return false; return false;
} }
return super.isSourceFile(filePath); return super.isSourceFile(filePath);
} }
readFile = (fileName: string) => this.context.readFile(fileName); readFile(fileName: string) {
const summary = this.librarySummaries.get(fileName);
if (summary) {
return summary.text;
}
return this.context.readFile(fileName);
}
getDefaultLibFileName = (options: ts.CompilerOptions) => getDefaultLibFileName = (options: ts.CompilerOptions) =>
this.context.getDefaultLibFileName(options) this.context.getDefaultLibFileName(options)
getCurrentDirectory = () => this.context.getCurrentDirectory(); getCurrentDirectory = () => this.context.getCurrentDirectory();
@ -451,12 +489,19 @@ function getPackageName(filePath: string): string|null {
export function relativeToRootDirs(filePath: string, rootDirs: string[]): string { export function relativeToRootDirs(filePath: string, rootDirs: string[]): string {
if (!filePath) return filePath; if (!filePath) return filePath;
for (const dir of rootDirs || []) { for (const dir of rootDirs || []) {
const rel = path.relative(dir, filePath); const rel = pathStartsWithPrefix(dir, filePath);
if (rel.indexOf('.') != 0) return rel; if (rel) {
return rel;
}
} }
return filePath; return filePath;
} }
function pathStartsWithPrefix(prefix: string, fullPath: string): string|null {
const rel = path.relative(prefix, fullPath);
return rel.startsWith('..') ? null : rel;
}
function stripNodeModulesPrefix(filePath: string): string { function stripNodeModulesPrefix(filePath: string): string {
return filePath.replace(/.*node_modules\//, ''); return filePath.replace(/.*node_modules\//, '');
} }
@ -473,12 +518,3 @@ function stripNgResourceSuffix(fileName: string): string {
function addNgResourceSuffix(fileName: string): string { function addNgResourceSuffix(fileName: string): string {
return `${fileName}.$ngresource$`; return `${fileName}.$ngresource$`;
} }
function summaryFileName(fileName: string): string {
const genFileMatch = GENERATED_FILES.exec(fileName);
if (genFileMatch) {
const base = genFileMatch[1];
return base + '.ngsummary.json';
}
return fileName.replace(EXT, '') + '.ngsummary.json';
}

View File

@ -14,8 +14,8 @@ import * as ts from 'typescript';
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics'; import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
import {ModuleMetadata, createBundleIndexHost} from '../metadata/index'; import {ModuleMetadata, createBundleIndexHost} from '../metadata/index';
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api'; import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
import {TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host'; import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions'; import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
import {getAngularEmitterTransformFactory} from './node_emitter_transform'; import {getAngularEmitterTransformFactory} from './node_emitter_transform';
import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from './util'; import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from './util';
@ -35,10 +35,10 @@ const defaultEmitCallback: TsEmitCallback =
class AngularCompilerProgram implements Program { class AngularCompilerProgram implements Program {
private metadataCache: LowerMetadataCache; private metadataCache: LowerMetadataCache;
private summariesFromPreviousCompilations = new Map<string, string>(); private oldProgramLibrarySummaries: LibrarySummary[] = [];
// Note: This will be cleared out as soon as we create the _tsProgram // Note: This will be cleared out as soon as we create the _tsProgram
private oldTsProgram: ts.Program|undefined; private oldTsProgram: ts.Program|undefined;
private _emittedGenFiles: GeneratedFile[]|undefined; private emittedLibrarySummaries: LibrarySummary[]|undefined;
// Lazily initialized fields // Lazily initialized fields
private _typeCheckHost: TypeCheckHost; private _typeCheckHost: TypeCheckHost;
@ -59,8 +59,7 @@ class AngularCompilerProgram implements Program {
} }
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined; this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
if (oldProgram) { if (oldProgram) {
oldProgram.getLibrarySummaries().forEach( this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries();
({content, fileName}) => this.summariesFromPreviousCompilations.set(fileName, content));
} }
if (options.flatModuleOutFile) { if (options.flatModuleOutFile) {
@ -82,21 +81,12 @@ class AngularCompilerProgram implements Program {
this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit); this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit);
} }
getLibrarySummaries(): {fileName: string, content: string}[] { getLibrarySummaries(): LibrarySummary[] {
const emittedLibSummaries: {fileName: string, content: string}[] = []; const result = [...this.oldProgramLibrarySummaries];
this.summariesFromPreviousCompilations.forEach( if (this.emittedLibrarySummaries) {
(content, fileName) => emittedLibSummaries.push({fileName, content})); result.push(...this.emittedLibrarySummaries);
if (this._emittedGenFiles) {
this._emittedGenFiles.forEach(genFile => {
if (genFile.srcFileUrl.endsWith('.d.ts') &&
genFile.genFileUrl.endsWith('.ngsummary.json')) {
// Note: ! is ok here as ngsummary.json files are always plain text, so genFile.source
// is filled.
emittedLibSummaries.push({fileName: genFile.genFileUrl, content: genFile.source !});
} }
}); return result;
}
return emittedLibSummaries;
} }
getTsProgram(): ts.Program { return this.tsProgram; } getTsProgram(): ts.Program { return this.tsProgram; }
@ -132,8 +122,8 @@ class AngularCompilerProgram implements Program {
if (this._analyzedModules) { if (this._analyzedModules) {
throw new Error('Angular structure already loaded'); throw new Error('Angular structure already loaded');
} }
const {tmpProgram, analyzedFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs(); const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
return this._compiler.loadFilesAsync(analyzedFiles) return this._compiler.loadFilesAsync(sourceFiles)
.catch(this.catchAnalysisError.bind(this)) .catch(this.catchAnalysisError.bind(this))
.then(analyzedModules => { .then(analyzedModules => {
if (this._analyzedModules) { if (this._analyzedModules) {
@ -159,7 +149,6 @@ class AngularCompilerProgram implements Program {
const bundle = this.compiler.emitMessageBundle(this.analyzedModules, locale); const bundle = this.compiler.emitMessageBundle(this.analyzedModules, locale);
i18nExtract(format, file, this.host, this.options, bundle); i18nExtract(format, file, this.host, this.options, bundle);
} }
const outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}> = [];
if ((emitFlags & (EmitFlags.JS | EmitFlags.DTS | EmitFlags.Metadata | EmitFlags.Codegen)) === if ((emitFlags & (EmitFlags.JS | EmitFlags.DTS | EmitFlags.Metadata | EmitFlags.Codegen)) ===
0) { 0) {
return {emitSkipped: true, diagnostics: [], emittedFiles: []}; return {emitSkipped: true, diagnostics: [], emittedFiles: []};
@ -172,6 +161,21 @@ class AngularCompilerProgram implements Program {
emittedFiles: [], emittedFiles: [],
}; };
} }
const emittedLibrarySummaries = this.emittedLibrarySummaries = [];
const outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}> = [];
const genFileByFileName = new Map<string, GeneratedFile>();
genFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile));
const writeTsFile: ts.WriteFileCallback =
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
let genFile: GeneratedFile|undefined;
if (sourceFile) {
outSrcMapping.push({outFileName: outFileName, sourceFile});
genFile = genFileByFileName.get(sourceFile.fileName);
}
this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
};
// Restore the original references before we emit so TypeScript doesn't emit // Restore the original references before we emit so TypeScript doesn't emit
// a reference to the .d.ts file. // a reference to the .d.ts file.
@ -183,14 +187,13 @@ class AngularCompilerProgram implements Program {
sourceFile.referencedFiles = originalReferences; sourceFile.referencedFiles = originalReferences;
} }
} }
let emitResult: ts.EmitResult; let emitResult: ts.EmitResult;
try { try {
emitResult = emitCallback({ emitResult = emitCallback({
program: this.tsProgram, program: this.tsProgram,
host: this.host, host: this.host,
options: this.options, options: this.options,
writeFile: createWriteFileCallback(genFiles, this.host, outSrcMapping), writeFile: writeTsFile,
emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS, emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS,
customTransformers: this.calculateTransforms(genFiles, customTransformers) customTransformers: this.calculateTransforms(genFiles, customTransformers)
}); });
@ -211,7 +214,8 @@ class AngularCompilerProgram implements Program {
if (emitFlags & EmitFlags.Codegen) { if (emitFlags & EmitFlags.Codegen) {
genFiles.forEach(gf => { genFiles.forEach(gf => {
if (gf.source) { if (gf.source) {
this.host.writeFile(srcToOutPath(gf.genFileUrl), gf.source, false); const outFileName = srcToOutPath(gf.genFileUrl);
this.writeFile(outFileName, gf.source, false, undefined, gf);
} }
}); });
} }
@ -220,8 +224,8 @@ class AngularCompilerProgram implements Program {
if (!sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName)) { if (!sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName)) {
const metadata = this.metadataCache.getMetadata(sf); const metadata = this.metadataCache.getMetadata(sf);
const metadataText = JSON.stringify([metadata]); const metadataText = JSON.stringify([metadata]);
this.host.writeFile( const outFileName = srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json'));
srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json')), metadataText, false); this.writeFile(outFileName, metadataText, false, undefined, undefined, [sf]);
} }
}); });
} }
@ -310,10 +314,10 @@ class AngularCompilerProgram implements Program {
if (this._analyzedModules) { if (this._analyzedModules) {
return; return;
} }
const {tmpProgram, analyzedFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs(); const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
let analyzedModules: NgAnalyzedModules; let analyzedModules: NgAnalyzedModules|null;
try { try {
analyzedModules = this._compiler.loadFilesSync(analyzedFiles); analyzedModules = this._compiler.loadFilesSync(sourceFiles);
} catch (e) { } catch (e) {
analyzedModules = this.catchAnalysisError(e); analyzedModules = this.catchAnalysisError(e);
} }
@ -322,9 +326,9 @@ class AngularCompilerProgram implements Program {
private _createProgramWithBasicStubs(): { private _createProgramWithBasicStubs(): {
tmpProgram: ts.Program, tmpProgram: ts.Program,
analyzedFiles: NgAnalyzedFile[],
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter,
rootNames: string[], rootNames: string[],
sourceFiles: string[],
} { } {
if (this._analyzedModules) { if (this._analyzedModules) {
throw new Error(`Internal Error: already initalized!`); throw new Error(`Internal Error: already initalized!`);
@ -332,19 +336,16 @@ class AngularCompilerProgram implements Program {
// Note: This is important to not produce a memory leak! // Note: This is important to not produce a memory leak!
const oldTsProgram = this.oldTsProgram; const oldTsProgram = this.oldTsProgram;
this.oldTsProgram = undefined; this.oldTsProgram = undefined;
const analyzedFiles: NgAnalyzedFile[] = [];
const codegen = (fileName: string) => { const codegen: CodeGenerator = {
if (this._analyzedModules) { generateFile: (genFileName, baseFileName) =>
throw new Error(`Internal Error: already initalized!`); this._compiler.emitBasicStub(genFileName, baseFileName),
} findGeneratedFileNames: (fileName) => this._compiler.findGeneratedFileNames(fileName),
const analyzedFile = this._compiler.analyzeFile(fileName);
analyzedFiles.push(analyzedFile);
const debug = fileName.endsWith('application_ref.ts');
return this._compiler.emitBasicStubs(analyzedFile);
}; };
const hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter( const hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter(
this.rootNames, this.options, this.host, this.metadataCache, codegen, this.rootNames, this.options, this.host, this.metadataCache, codegen,
this.summariesFromPreviousCompilations); this.oldProgramLibrarySummaries);
const aotOptions = getAotCompilerOptions(this.options); const aotOptions = getAotCompilerOptions(this.options);
this._compiler = createAotCompiler(hostAdapter, aotOptions).compiler; this._compiler = createAotCompiler(hostAdapter, aotOptions).compiler;
this._typeCheckHost = hostAdapter; this._typeCheckHost = hostAdapter;
@ -354,26 +355,41 @@ class AngularCompilerProgram implements Program {
this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn)); this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn));
if (this.options.noResolve) { if (this.options.noResolve) {
this.rootNames.forEach(rootName => { this.rootNames.forEach(rootName => {
const sf = if (hostAdapter.shouldGenerateFilesFor(rootName)) {
hostAdapter.getSourceFile(rootName, this.options.target || ts.ScriptTarget.Latest); rootNames.push(...this._compiler.findGeneratedFileNames(rootName));
sf.referencedFiles.forEach((fileRef) => {
if (GENERATED_FILES.test(fileRef.fileName)) {
rootNames.push(fileRef.fileName);
} }
}); });
});
} }
const tmpProgram = ts.createProgram(rootNames, this.options, hostAdapter, oldTsProgram); const tmpProgram = ts.createProgram(rootNames, this.options, hostAdapter, oldTsProgram);
return {tmpProgram, analyzedFiles, hostAdapter, rootNames}; const sourceFiles: string[] = [];
tmpProgram.getSourceFiles().forEach(sf => {
if (hostAdapter.isSourceFile(sf.fileName)) {
sourceFiles.push(sf.fileName);
}
});
return {tmpProgram, sourceFiles, hostAdapter, rootNames};
} }
private _updateProgramWithTypeCheckStubs( private _updateProgramWithTypeCheckStubs(
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules, tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules|null,
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) { hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) {
this._analyzedModules = analyzedModules; this._analyzedModules = analyzedModules || emptyModules;
const genFiles = this._compiler.emitTypeCheckStubs(analyzedModules); if (analyzedModules) {
genFiles.forEach(gf => hostAdapter.updateGeneratedFile(gf)); tmpProgram.getSourceFiles().forEach(sf => {
if (sf.fileName.endsWith('.ngfactory.ts')) {
const {generate, baseFileName} = hostAdapter.shouldGenerateFile(sf.fileName);
if (generate) {
// Note: ! is ok as hostAdapter.shouldGenerateFile will always return a basefileName
// for .ngfactory.ts files.
const genFile = this._compiler.emitTypeCheckStub(sf.fileName, baseFileName !);
if (genFile) {
hostAdapter.updateGeneratedFile(genFile);
}
}
}
});
}
this._tsProgram = ts.createProgram(rootNames, this.options, hostAdapter, tmpProgram); this._tsProgram = ts.createProgram(rootNames, this.options, hostAdapter, tmpProgram);
// Note: the new ts program should be completely reusable by TypeScript as: // Note: the new ts program should be completely reusable by TypeScript as:
// - we cache all the files in the hostAdapter // - we cache all the files in the hostAdapter
@ -384,7 +400,7 @@ class AngularCompilerProgram implements Program {
} }
} }
private catchAnalysisError(e: any): NgAnalyzedModules { private catchAnalysisError(e: any): NgAnalyzedModules|null {
if (isSyntaxError(e)) { if (isSyntaxError(e)) {
const parserErrors = getParseErrors(e); const parserErrors = getParseErrors(e);
if (parserErrors && parserErrors.length) { if (parserErrors && parserErrors.length) {
@ -404,7 +420,7 @@ class AngularCompilerProgram implements Program {
code: DEFAULT_ERROR_CODE code: DEFAULT_ERROR_CODE
}]; }];
} }
return emptyModules; return null;
} }
throw e; throw e;
} }
@ -417,7 +433,7 @@ class AngularCompilerProgram implements Program {
if (!(emitFlags & EmitFlags.Codegen)) { if (!(emitFlags & EmitFlags.Codegen)) {
return {genFiles: [], genDiags: []}; return {genFiles: [], genDiags: []};
} }
const genFiles = this._emittedGenFiles = this.compiler.emitAllImpls(this.analyzedModules); const genFiles = this.compiler.emitAllImpls(this.analyzedModules);
return {genFiles, genDiags: []}; return {genFiles, genDiags: []};
} catch (e) { } catch (e) {
// TODO(tbosch): check whether we can actually have syntax errors here, // TODO(tbosch): check whether we can actually have syntax errors here,
@ -441,6 +457,51 @@ class AngularCompilerProgram implements Program {
private generateSemanticDiagnostics(): {ts: ts.Diagnostic[], ng: Diagnostic[]} { private generateSemanticDiagnostics(): {ts: ts.Diagnostic[], ng: Diagnostic[]} {
return translateDiagnostics(this.typeCheckHost, this.tsProgram.getSemanticDiagnostics()); return translateDiagnostics(this.typeCheckHost, this.tsProgram.getSemanticDiagnostics());
} }
private writeFile(
outFileName: string, outData: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, genFile?: GeneratedFile, sourceFiles?: ts.SourceFile[]) {
// collect emittedLibrarySummaries
let baseFile: ts.SourceFile|undefined;
if (genFile) {
baseFile = this.tsProgram.getSourceFile(genFile.srcFileUrl);
if (baseFile) {
if (!this.emittedLibrarySummaries) {
this.emittedLibrarySummaries = [];
}
if (genFile.genFileUrl.endsWith('.ngsummary.json') && baseFile.fileName.endsWith('.d.ts')) {
this.emittedLibrarySummaries.push({
fileName: baseFile.fileName,
text: baseFile.text,
sourceFile: baseFile,
});
this.emittedLibrarySummaries.push({fileName: genFile.genFileUrl, text: outData});
} else if (outFileName.endsWith('.d.ts') && baseFile.fileName.endsWith('.d.ts')) {
const dtsSourceFilePath = genFile.genFileUrl.replace(/\.ts$/, '.d.ts');
// Note: Don't use sourceFiles here as the created .d.ts has a path in the outDir,
// but we need one that is next to the .ts file
this.emittedLibrarySummaries.push({fileName: dtsSourceFilePath, text: outData});
}
}
}
// Filter out generated files for which we didn't generate code.
// This can happen as the stub caclulation is not completely exact.
// Note: sourceFile refers to the .ngfactory.ts / .ngsummary.ts file
const isGenerated = GENERATED_FILES.test(outFileName);
if (isGenerated) {
if (!genFile || !genFile.stmts || genFile.stmts.length === 0) {
if (this.options.allowEmptyCodegenFiles) {
outData = '';
} else {
return;
}
}
}
if (baseFile) {
sourceFiles = sourceFiles ? [...sourceFiles, baseFile] : [baseFile];
}
this.host.writeFile(outFileName, outData, writeByteOrderMark, onError, sourceFiles);
}
} }
export function createProgram( export function createProgram(
@ -483,31 +544,7 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
enableSummariesForJit: true, enableSummariesForJit: true,
preserveWhitespaces: options.preserveWhitespaces, preserveWhitespaces: options.preserveWhitespaces,
fullTemplateTypeCheck: options.fullTemplateTypeCheck, fullTemplateTypeCheck: options.fullTemplateTypeCheck,
rootDir: options.rootDir, allowEmptyCodegenFiles: options.allowEmptyCodegenFiles,
};
}
function createWriteFileCallback(
generatedFiles: GeneratedFile[], host: ts.CompilerHost,
outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}>) {
const genFileByFileName = new Map<string, GeneratedFile>();
generatedFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile));
return (fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
if (sourceFile) {
outSrcMapping.push({outFileName: fileName, sourceFile});
}
const isGenerated = GENERATED_FILES.test(fileName);
if (isGenerated && sourceFile) {
// Filter out generated files for which we didn't generate code.
// This can happen as the stub caclulation is not completely exact.
const genFile = genFileByFileName.get(sourceFile.fileName);
if (!genFile || !genFile.stmts || genFile.stmts.length === 0) {
return;
}
}
host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
}; };
} }

View File

@ -713,13 +713,13 @@ describe('ngc transformer command-line', () => {
}); });
}); });
it('should be able to generate a flat module library', () => { function writeFlatModule(outFile: string) {
writeConfig(` writeConfig(`
{ {
"extends": "./tsconfig-base.json", "extends": "./tsconfig-base.json",
"angularCompilerOptions": { "angularCompilerOptions": {
"flatModuleId": "flat_module", "flatModuleId": "flat_module",
"flatModuleOutFile": "index.js", "flatModuleOutFile": "${outFile}",
"skipTemplateCodegen": true "skipTemplateCodegen": true
}, },
"files": ["public-api.ts"] "files": ["public-api.ts"]
@ -753,6 +753,10 @@ describe('ngc transformer command-line', () => {
}) })
export class FlatModule { export class FlatModule {
}`); }`);
}
it('should be able to generate a flat module library', () => {
writeFlatModule('index.js');
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0); expect(exitCode).toEqual(0);
@ -760,6 +764,60 @@ describe('ngc transformer command-line', () => {
shouldExist('index.metadata.json'); shouldExist('index.metadata.json');
}); });
it('should use the importAs for flat libraries instead of deep imports', () => {
// compile the flat module
writeFlatModule('index.js');
expect(main(['-p', basePath], errorSpy)).toBe(0);
// move the flat module output into node_modules
const flatModuleNodeModulesPath = path.resolve(basePath, 'node_modules', 'flat_module');
fs.renameSync(outDir, flatModuleNodeModulesPath);
fs.renameSync(
path.resolve(basePath, 'src/flat.component.html'),
path.resolve(flatModuleNodeModulesPath, 'src/flat.component.html'));
// add a package.json
fs.writeFileSync(
path.resolve(flatModuleNodeModulesPath, 'package.json'), `{"typings": "./index.d.ts"}`);
// and remove the sources.
fs.renameSync(path.resolve(basePath, 'src'), path.resolve(basePath, 'flat_module_src'));
fs.unlinkSync(path.resolve(basePath, 'public-api.ts'));
writeConfig(`
{
"extends": "./tsconfig-base.json",
"files": ["index.ts"]
}
`);
write('index.ts', `
import {NgModule} from '@angular/core';
import {FlatModule} from 'flat_module';
@NgModule({
imports: [FlatModule]
})
export class MyModule {}
`);
expect(main(['-p', basePath], errorSpy)).toBe(0);
shouldExist('index.js');
const summary =
fs.readFileSync(path.resolve(basePath, 'built', 'index.ngsummary.json')).toString();
// reference to the module itself
expect(summary).toMatch(/"filePath":"flat_module"/);
// no reference to a deep file
expect(summary).not.toMatch(/"filePath":"flat_module\//);
const factory =
fs.readFileSync(path.resolve(basePath, 'built', 'index.ngfactory.js')).toString();
// reference to the module itself
expect(factory).toMatch(/from "flat_module"/);
// no reference to a deep file
expect(factory).not.toMatch(/from "flat_module\//);
});
describe('with tree example', () => { describe('with tree example', () => {
beforeEach(() => { beforeEach(() => {
writeConfig(); writeConfig();

View File

@ -10,7 +10,7 @@ import * as compiler from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {MetadataCollector} from '../../src/metadata/collector'; import {MetadataCollector} from '../../src/metadata/collector';
import {CompilerHost, CompilerOptions} from '../../src/transformers/api'; import {CompilerHost, CompilerOptions, LibrarySummary} from '../../src/transformers/api';
import {TsCompilerAotCompilerTypeCheckHostAdapter, createCompilerHost} from '../../src/transformers/compiler_host'; import {TsCompilerAotCompilerTypeCheckHostAdapter, createCompilerHost} from '../../src/transformers/compiler_host';
import {Directory, Entry, MockAotContext, MockCompilerHost} from '../mocks'; import {Directory, Entry, MockAotContext, MockCompilerHost} from '../mocks';
@ -21,9 +21,14 @@ const aGeneratedFile = new compiler.GeneratedFile(
const aGeneratedFileText = `var x:any = 1;\n`; const aGeneratedFileText = `var x:any = 1;\n`;
describe('NgCompilerHost', () => { describe('NgCompilerHost', () => {
let codeGenerator: jasmine.Spy; let codeGenerator: {generateFile: jasmine.Spy; findGeneratedFileNames: jasmine.Spy;};
beforeEach(() => { codeGenerator = jasmine.createSpy('codeGenerator').and.returnValue([]); }); beforeEach(() => {
codeGenerator = {
generateFile: jasmine.createSpy('generateFile').and.returnValue(null),
findGeneratedFileNames: jasmine.createSpy('findGeneratedFileNames').and.returnValue([]),
};
});
function createNgHost({files = {}}: {files?: Directory} = {}): CompilerHost { function createNgHost({files = {}}: {files?: Directory} = {}): CompilerHost {
const context = new MockAotContext('/tmp/', files); const context = new MockAotContext('/tmp/', files);
@ -37,16 +42,16 @@ describe('NgCompilerHost', () => {
moduleResolution: ts.ModuleResolutionKind.NodeJs, moduleResolution: ts.ModuleResolutionKind.NodeJs,
}, },
ngHost = createNgHost({files}), ngHost = createNgHost({files}),
summariesFromPreviousCompilations = new Map<string, string>(), librarySummaries = [],
}: { }: {
files?: Directory, files?: Directory,
options?: CompilerOptions, options?: CompilerOptions,
ngHost?: CompilerHost, ngHost?: CompilerHost,
summariesFromPreviousCompilations?: Map<string, string> librarySummaries?: LibrarySummary[]
} = {}) { } = {}) {
return new TsCompilerAotCompilerTypeCheckHostAdapter( return new TsCompilerAotCompilerTypeCheckHostAdapter(
['/tmp/index.ts'], options, ngHost, new MetadataCollector(), codeGenerator, ['/tmp/index.ts'], options, ngHost, new MetadataCollector(), codeGenerator,
summariesFromPreviousCompilations); librarySummaries);
} }
describe('fileNameToModuleName', () => { describe('fileNameToModuleName', () => {
@ -180,7 +185,8 @@ describe('NgCompilerHost', () => {
}); });
it('should generate code when asking for the base name and add it as referencedFiles', () => { it('should generate code when asking for the base name and add it as referencedFiles', () => {
codeGenerator.and.returnValue([aGeneratedFile]); codeGenerator.findGeneratedFileNames.and.returnValue(['/tmp/src/index.ngfactory.ts']);
codeGenerator.generateFile.and.returnValue(aGeneratedFile);
const host = createHost({ const host = createHost({
files: { files: {
'tmp': { 'tmp': {
@ -201,11 +207,13 @@ describe('NgCompilerHost', () => {
expect(genSf.text).toBe(aGeneratedFileText); expect(genSf.text).toBe(aGeneratedFileText);
// the codegen should have been cached // the codegen should have been cached
expect(codeGenerator).toHaveBeenCalledTimes(1); expect(codeGenerator.generateFile).toHaveBeenCalledTimes(1);
expect(codeGenerator.findGeneratedFileNames).toHaveBeenCalledTimes(1);
}); });
it('should generate code when asking for the generated name first', () => { it('should generate code when asking for the generated name first', () => {
codeGenerator.and.returnValue([aGeneratedFile]); codeGenerator.findGeneratedFileNames.and.returnValue(['/tmp/src/index.ngfactory.ts']);
codeGenerator.generateFile.and.returnValue(aGeneratedFile);
const host = createHost({ const host = createHost({
files: { files: {
'tmp': { 'tmp': {
@ -226,10 +234,13 @@ describe('NgCompilerHost', () => {
expect(sf.referencedFiles[1].fileName).toBe('/tmp/src/index.ngfactory.ts'); expect(sf.referencedFiles[1].fileName).toBe('/tmp/src/index.ngfactory.ts');
// the codegen should have been cached // the codegen should have been cached
expect(codeGenerator).toHaveBeenCalledTimes(1); expect(codeGenerator.generateFile).toHaveBeenCalledTimes(1);
expect(codeGenerator.findGeneratedFileNames).toHaveBeenCalledTimes(1);
}); });
it('should clear old generated references if the original host cached them', () => { it('should clear old generated references if the original host cached them', () => {
codeGenerator.findGeneratedFileNames.and.returnValue(['/tmp/src/index.ngfactory.ts']);
const ngHost = createNgHost(); const ngHost = createNgHost();
const sfText = ` const sfText = `
/// <reference path="main.ts"/> /// <reference path="main.ts"/>
@ -237,8 +248,9 @@ describe('NgCompilerHost', () => {
const sf = ts.createSourceFile('/tmp/src/index.ts', sfText, ts.ScriptTarget.Latest); const sf = ts.createSourceFile('/tmp/src/index.ts', sfText, ts.ScriptTarget.Latest);
ngHost.getSourceFile = () => sf; ngHost.getSourceFile = () => sf;
codeGenerator.and.returnValue( codeGenerator.findGeneratedFileNames.and.returnValue(['/tmp/src/index.ngfactory.ts']);
[new compiler.GeneratedFile('/tmp/src/index.ts', '/tmp/src/index.ngfactory.ts', [])]); codeGenerator.generateFile.and.returnValue(
new compiler.GeneratedFile('/tmp/src/index.ts', '/tmp/src/index.ngfactory.ts', []));
const host1 = createHost({ngHost}); const host1 = createHost({ngHost});
host1.getSourceFile('/tmp/src/index.ts', ts.ScriptTarget.Latest); host1.getSourceFile('/tmp/src/index.ts', ts.ScriptTarget.Latest);
@ -246,7 +258,8 @@ describe('NgCompilerHost', () => {
expect(sf.referencedFiles[0].fileName).toBe('main.ts'); expect(sf.referencedFiles[0].fileName).toBe('main.ts');
expect(sf.referencedFiles[1].fileName).toBe('/tmp/src/index.ngfactory.ts'); expect(sf.referencedFiles[1].fileName).toBe('/tmp/src/index.ngfactory.ts');
codeGenerator.and.returnValue([]); codeGenerator.findGeneratedFileNames.and.returnValue([]);
codeGenerator.generateFile.and.returnValue(null);
const host2 = createHost({ngHost}); const host2 = createHost({ngHost});
host2.getSourceFile('/tmp/src/index.ts', ts.ScriptTarget.Latest); host2.getSourceFile('/tmp/src/index.ts', ts.ScriptTarget.Latest);
@ -257,7 +270,8 @@ describe('NgCompilerHost', () => {
describe('updateSourceFile', () => { describe('updateSourceFile', () => {
it('should update source files', () => { it('should update source files', () => {
codeGenerator.and.returnValue([aGeneratedFile]); codeGenerator.findGeneratedFileNames.and.returnValue(['/tmp/src/index.ngfactory.ts']);
codeGenerator.generateFile.and.returnValue(aGeneratedFile);
const host = createHost({files: {'tmp': {'src': {'index.ts': ''}}}}); const host = createHost({files: {'tmp': {'src': {'index.ts': ''}}}});
let genSf = host.getSourceFile('/tmp/src/index.ngfactory.ts', ts.ScriptTarget.Latest); let genSf = host.getSourceFile('/tmp/src/index.ngfactory.ts', ts.ScriptTarget.Latest);
@ -271,11 +285,12 @@ describe('NgCompilerHost', () => {
}); });
it('should error if the imports changed', () => { it('should error if the imports changed', () => {
codeGenerator.and.returnValue( codeGenerator.findGeneratedFileNames.and.returnValue(['/tmp/src/index.ngfactory.ts']);
[new compiler.GeneratedFile('/tmp/src/index.ts', '/tmp/src/index.ngfactory.ts', [ codeGenerator.generateFile.and.returnValue(new compiler.GeneratedFile(
new compiler.DeclareVarStmt( '/tmp/src/index.ts', '/tmp/src/index.ngfactory.ts',
'x', new compiler.ExternalExpr(new compiler.ExternalReference('aModule', 'aName'))) [new compiler.DeclareVarStmt(
])]); 'x',
new compiler.ExternalExpr(new compiler.ExternalReference('aModule', 'aName')))]));
const host = createHost({files: {'tmp': {'src': {'index.ts': ''}}}}); const host = createHost({files: {'tmp': {'src': {'index.ts': ''}}}});
host.getSourceFile('/tmp/src/index.ngfactory.ts', ts.ScriptTarget.Latest); host.getSourceFile('/tmp/src/index.ngfactory.ts', ts.ScriptTarget.Latest);
@ -292,32 +307,4 @@ describe('NgCompilerHost', () => {
].join('\n')); ].join('\n'));
}); });
}); });
describe('fileExists', () => {
it('should cache calls', () => {
const ngHost = createNgHost({files: {'tmp': {'src': {'index.ts': ``}}}});
spyOn(ngHost, 'fileExists').and.callThrough();
const host = createHost({ngHost});
expect(host.fileExists('/tmp/src/index.ts')).toBe(true);
expect(host.fileExists('/tmp/src/index.ts')).toBe(true);
expect(ngHost.fileExists).toHaveBeenCalledTimes(1);
});
it(`should not derive the existence of generated files baesd on summaries on disc`, () => {
const host = createHost({files: {'tmp': {'lib': {'module.ngsummary.json': ``}}}});
expect(host.fileExists('/tmp/lib/module.ngfactory.ts')).toBe(false);
expect(host.fileExists('/tmp/lib/module.ngfactory.d.ts')).toBe(false);
});
it(`should derive the existence of generated .d.ts files based on the summaries from an old program`,
() => {
const summariesFromPreviousCompilations = new Map<string, string>();
summariesFromPreviousCompilations.set('/tmp/lib/module.ngsummary.json', `{}`);
const host = createHost({summariesFromPreviousCompilations});
expect(host.fileExists('/tmp/lib/module.ngfactory.ts')).toBe(false);
expect(host.fileExists('/tmp/lib/module.ngfactory.d.ts')).toBe(true);
});
});
}); });

View File

@ -38,15 +38,11 @@ describe('ng program', () => {
`; `;
} }
describe('reuse of old program', () => {
function compileLib(libName: string) { function compileLib(libName: string) {
testSupport.writeFiles({ testSupport.writeFiles({
[`${libName}_src/index.ts`]: createModuleAndCompSource(libName), [`${libName}_src/index.ts`]: createModuleAndCompSource(libName),
}); });
const options = testSupport.createCompilerOptions({ const options = testSupport.createCompilerOptions();
skipTemplateCodegen: true,
});
const program = ng.createProgram({ const program = ng.createProgram({
rootNames: [path.resolve(testSupport.basePath, `${libName}_src/index.ts`)], rootNames: [path.resolve(testSupport.basePath, `${libName}_src/index.ts`)],
options, options,
@ -59,13 +55,17 @@ describe('ng program', () => {
program.emit({emitFlags: ng.EmitFlags.DTS | ng.EmitFlags.JS | ng.EmitFlags.Metadata}); program.emit({emitFlags: ng.EmitFlags.DTS | ng.EmitFlags.JS | ng.EmitFlags.Metadata});
} }
function compile(oldProgram?: ng.Program): ng.Program { function compile(
const options = testSupport.createCompilerOptions(); oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions,
const rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')]; rootNames?: string[]): ng.Program {
const options = testSupport.createCompilerOptions(overrideOptions);
if (!rootNames) {
rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')];
}
const program = ng.createProgram({ const program = ng.createProgram({
rootNames: rootNames, rootNames: rootNames,
options: testSupport.createCompilerOptions(), options,
host: ng.createCompilerHost({options}), oldProgram, host: ng.createCompilerHost({options}), oldProgram,
}); });
expectNoDiagnosticsInProgram(options, program); expectNoDiagnosticsInProgram(options, program);
@ -73,6 +73,7 @@ describe('ng program', () => {
return program; return program;
} }
describe('reuse of old program', () => {
it('should reuse generated code for libraries from old programs', () => { it('should reuse generated code for libraries from old programs', () => {
compileLib('lib'); compileLib('lib');
testSupport.writeFiles({ testSupport.writeFiles({
@ -123,6 +124,29 @@ describe('ng program', () => {
.toBe(false); .toBe(false);
}); });
it('should store library summaries on emit', () => {
compileLib('lib');
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main'),
'src/index.ts': `
export * from './main';
export * from 'lib/index';
`
});
const p1 = compile();
expect(p1.getLibrarySummaries().some(
sf => /node_modules\/lib\/index\.ngfactory\.d\.ts$/.test(sf.fileName)))
.toBe(true);
expect(p1.getLibrarySummaries().some(
sf => /node_modules\/lib\/index\.ngsummary\.json$/.test(sf.fileName)))
.toBe(true);
expect(
p1.getLibrarySummaries().some(sf => /node_modules\/lib\/index\.d\.ts$/.test(sf.fileName)))
.toBe(true);
expect(p1.getLibrarySummaries().some(sf => /src\/main.*$/.test(sf.fileName))).toBe(false);
});
it('should reuse the old ts program completely if nothing changed', () => { it('should reuse the old ts program completely if nothing changed', () => {
testSupport.writeFiles({'src/index.ts': createModuleAndCompSource('main')}); testSupport.writeFiles({'src/index.ts': createModuleAndCompSource('main')});
// Note: the second compile drops factories for library files, // Note: the second compile drops factories for library files,
@ -223,13 +247,118 @@ describe('ng program', () => {
const allRootNames = preProgram.getSourceFiles().map(sf => sf.fileName); const allRootNames = preProgram.getSourceFiles().map(sf => sf.fileName);
// now do the actual test with noResolve // now do the actual test with noResolve
const options = testSupport.createCompilerOptions({noResolve: true}); const program = compile(undefined, {noResolve: true}, allRootNames);
const host = ng.createCompilerHost({options});
const program = ng.createProgram({rootNames: allRootNames, options, host});
expectNoDiagnosticsInProgram(options, program);
program.emit();
testSupport.shouldExist('built/src/main.ngfactory.js'); testSupport.shouldExist('built/src/main.ngfactory.js');
testSupport.shouldExist('built/src/main.ngfactory.d.ts'); testSupport.shouldExist('built/src/main.ngfactory.d.ts');
}); });
it('should emit also empty generated files depending on the options', () => {
testSupport.writeFiles({
'src/main.ts': `
import {Component, NgModule} from '@angular/core';
@Component({selector: 'main', template: '', styleUrls: ['main.css']})
export class MainComp {}
@NgModule({declarations: [MainComp]})
export class MainModule {}
`,
'src/main.css': ``,
'src/util.ts': 'export const x = 1;',
'src/index.ts': `
export * from './util';
export * from './main';
`,
});
const options = testSupport.createCompilerOptions({allowEmptyCodegenFiles: true});
const host = ng.createCompilerHost({options});
const written = new Map < string, {
original: ts.SourceFile[]|undefined;
data: string;
}
> ();
host.writeFile =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
written.set(fileName, {original: sourceFiles, data});
};
const program = ng.createProgram(
{rootNames: [path.resolve(testSupport.basePath, 'src/index.ts')], options, host});
program.emit();
function assertGenFile(
fileName: string, checks: {originalFileName: string, shouldBeEmpty: boolean}) {
const writeData = written.get(path.join(testSupport.basePath, fileName));
expect(writeData).toBeTruthy();
expect(writeData !.original !.some(
sf => sf.fileName === path.join(testSupport.basePath, checks.originalFileName)))
.toBe(true);
if (checks.shouldBeEmpty) {
expect(writeData !.data).toBe('');
} else {
expect(writeData !.data).not.toBe('');
}
}
assertGenFile(
'built/src/util.ngfactory.js', {originalFileName: 'src/util.ts', shouldBeEmpty: true});
assertGenFile(
'built/src/util.ngfactory.d.ts', {originalFileName: 'src/util.ts', shouldBeEmpty: true});
assertGenFile(
'built/src/util.ngsummary.js', {originalFileName: 'src/util.ts', shouldBeEmpty: true});
assertGenFile(
'built/src/util.ngsummary.d.ts', {originalFileName: 'src/util.ts', shouldBeEmpty: true});
assertGenFile(
'built/src/util.ngsummary.json', {originalFileName: 'src/util.ts', shouldBeEmpty: false});
// Note: we always fill non shim and shim style files as they might
// be shared by component with and without ViewEncapsulation.
assertGenFile(
'built/src/main.css.ngstyle.js', {originalFileName: 'src/main.ts', shouldBeEmpty: false});
assertGenFile(
'built/src/main.css.ngstyle.d.ts', {originalFileName: 'src/main.ts', shouldBeEmpty: true});
// Note: this file is not empty as we actually generated code for it
assertGenFile(
'built/src/main.css.shim.ngstyle.js',
{originalFileName: 'src/main.ts', shouldBeEmpty: false});
assertGenFile(
'built/src/main.css.shim.ngstyle.d.ts',
{originalFileName: 'src/main.ts', shouldBeEmpty: true});
});
it('should not emit /// references in .d.ts files', () => {
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main'),
});
compile(undefined, {declaration: true}, [path.resolve(testSupport.basePath, 'src/main.ts')]);
const dts =
fs.readFileSync(path.resolve(testSupport.basePath, 'built', 'src', 'main.d.ts')).toString();
expect(dts).toMatch('export declare class');
expect(dts).not.toMatch('///');
});
it('should not emit generated files whose sources are outside of the rootDir', () => {
compileLib('lib');
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main'),
'src/index.ts': `
export * from './main';
export * from 'lib/index';
`
});
compile(undefined, {rootDir: path.resolve(testSupport.basePath, 'src')});
testSupport.shouldExist('built/main.js');
testSupport.shouldExist('built/main.d.ts');
testSupport.shouldExist('built/main.ngfactory.js');
testSupport.shouldExist('built/main.ngfactory.d.ts');
testSupport.shouldExist('built/main.ngsummary.json');
testSupport.shouldNotExist('build/node_modules/lib/index.js');
testSupport.shouldNotExist('build/node_modules/lib/index.d.ts');
testSupport.shouldNotExist('build/node_modules/lib/index.ngfactory.js');
testSupport.shouldNotExist('build/node_modules/lib/index.ngfactory.d.ts');
testSupport.shouldNotExist('build/node_modules/lib/index.ngsummary.json');
});
}); });

View File

@ -44,9 +44,10 @@ enum StubEmitFlags {
export class AotCompiler { export class AotCompiler {
private _templateAstCache = private _templateAstCache =
new Map<StaticSymbol, {template: TemplateAst[], pipes: CompilePipeSummary[]}>(); new Map<StaticSymbol, {template: TemplateAst[], pipes: CompilePipeSummary[]}>();
private _analyzedFiles = new Map<string, NgAnalyzedFile>();
constructor( constructor(
private _config: CompilerConfig, private options: AotCompilerOptions, private _config: CompilerConfig, private _options: AotCompilerOptions,
private _host: AotCompilerHost, private _reflector: StaticReflector, private _host: AotCompilerHost, private _reflector: StaticReflector,
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser, private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
@ -76,23 +77,99 @@ export class AotCompiler {
.then(() => analyzeResult); .then(() => analyzeResult);
} }
analyzeFile(fileName: string): NgAnalyzedFile { private _analyzeFile(fileName: string): NgAnalyzedFile {
return analyzeFile(this._host, this._symbolResolver, this._metadataResolver, fileName); let analyzedFile = this._analyzedFiles.get(fileName);
if (!analyzedFile) {
analyzedFile =
analyzeFile(this._host, this._symbolResolver, this._metadataResolver, fileName);
this._analyzedFiles.set(fileName, analyzedFile);
}
return analyzedFile;
} }
emitBasicStubs(file: NgAnalyzedFile): GeneratedFile[] { findGeneratedFileNames(fileName: string): string[] {
return this._emitStubs(file, StubEmitFlags.Basic); const genFileNames: string[] = [];
const file = this._analyzeFile(fileName);
// Make sure we create a .ngfactory if we have a injectable/directive/pipe/NgModule
// or a reference to a non source file.
// Note: This is overestimating the required .ngfactory files as the real calculation is harder.
// Only do this for StubEmitFlags.Basic, as adding a type check block
// does not change this file (as we generate type check blocks based on NgModules).
if (this._options.allowEmptyCodegenFiles || file.directives.length || file.pipes.length ||
file.injectables.length || file.ngModules.length || file.exportsNonSourceFiles) {
genFileNames.push(ngfactoryFilePath(file.fileName, true));
genFileNames.push(summaryForJitFileName(file.fileName, true));
}
const fileSuffix = splitTypescriptSuffix(file.fileName, true)[1];
file.directives.forEach((dirSymbol) => {
const compMeta =
this._metadataResolver.getNonNormalizedDirectiveMetadata(dirSymbol) !.metadata;
if (!compMeta.isComponent) {
return;
}
// Note: compMeta is a component and therefore template is non null.
compMeta.template !.styleUrls.forEach((styleUrl) => {
const normalizedUrl = this._host.resourceNameToFileName(styleUrl, file.fileName);
if (!normalizedUrl) {
throw new Error(`Couldn't resolve resource ${styleUrl} relative to ${file.fileName}`);
}
const needsShim = (compMeta.template !.encapsulation ||
this._config.defaultEncapsulation) === ViewEncapsulation.Emulated;
genFileNames.push(_stylesModuleUrl(normalizedUrl, needsShim, fileSuffix));
if (this._options.allowEmptyCodegenFiles) {
genFileNames.push(_stylesModuleUrl(normalizedUrl, !needsShim, fileSuffix));
}
});
});
return genFileNames;
} }
emitTypeCheckStubs(files: NgAnalyzedModules): GeneratedFile[] { emitBasicStub(genFileName: string, originalFileName?: string): GeneratedFile {
const generatedFiles: GeneratedFile[] = []; const outputCtx = this._createOutputContext(genFileName);
files.files.forEach( if (genFileName.endsWith('.ngfactory.ts')) {
file => this._emitStubs(file, StubEmitFlags.TypeCheck) if (!originalFileName) {
.forEach(genFile => generatedFiles.push(genFile))); throw new Error(
return generatedFiles; `Assertion error: require the original file for .ngfactory.ts stubs. File: ${genFileName}`);
}
const originalFile = this._analyzeFile(originalFileName);
this._createNgFactoryStub(outputCtx, originalFile, StubEmitFlags.Basic);
} else if (genFileName.endsWith('.ngsummary.ts')) {
if (this._options.enableSummariesForJit) {
if (!originalFileName) {
throw new Error(
`Assertion error: require the original file for .ngsummary.ts stubs. File: ${genFileName}`);
}
const originalFile = this._analyzeFile(originalFileName);
_createEmptyStub(outputCtx);
originalFile.ngModules.forEach(ngModule => {
// create exports that user code can reference
createForJitStub(outputCtx, ngModule.type.reference);
});
}
} else if (genFileName.endsWith('.ngstyle.ts')) {
_createEmptyStub(outputCtx);
}
// Note: for the stubs, we don't need a property srcFileUrl,
// as lateron in emitAllImpls we will create the proper GeneratedFiles with the
// correct srcFileUrl.
// This is good as e.g. for .ngstyle.ts files we can't derive
// the url of components based on the genFileUrl.
return this._codegenSourceModule('unknown', outputCtx);
} }
loadFilesAsync(files: NgAnalyzedFile[]): Promise<NgAnalyzedModules> { emitTypeCheckStub(genFileName: string, originalFileName: string): GeneratedFile|null {
const originalFile = this._analyzeFile(originalFileName);
const outputCtx = this._createOutputContext(genFileName);
if (genFileName.endsWith('.ngfactory.ts')) {
this._createNgFactoryStub(outputCtx, originalFile, StubEmitFlags.TypeCheck);
}
return outputCtx.statements.length > 0 ?
this._codegenSourceModule(originalFile.fileName, outputCtx) :
null;
}
loadFilesAsync(fileNames: string[]): Promise<NgAnalyzedModules> {
const files = fileNames.map(fileName => this._analyzeFile(fileName));
const loadingPromises: Promise<NgAnalyzedModules>[] = []; const loadingPromises: Promise<NgAnalyzedModules>[] = [];
files.forEach( files.forEach(
file => file.ngModules.forEach( file => file.ngModules.forEach(
@ -102,7 +179,8 @@ export class AotCompiler {
return Promise.all(loadingPromises).then(_ => mergeAndValidateNgFiles(files)); return Promise.all(loadingPromises).then(_ => mergeAndValidateNgFiles(files));
} }
loadFilesSync(files: NgAnalyzedFile[]): NgAnalyzedModules { loadFilesSync(fileNames: string[]): NgAnalyzedModules {
const files = fileNames.map(fileName => this._analyzeFile(fileName));
files.forEach( files.forEach(
file => file.ngModules.forEach( file => file.ngModules.forEach(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata( ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
@ -110,19 +188,8 @@ export class AotCompiler {
return mergeAndValidateNgFiles(files); return mergeAndValidateNgFiles(files);
} }
private _emitStubs(file: NgAnalyzedFile, emitFlags: StubEmitFlags): GeneratedFile[] { private _createNgFactoryStub(
return [ outputCtx: OutputContext, file: NgAnalyzedFile, emitFlags: StubEmitFlags) {
...this._createNgFactoryStub(file, emitFlags),
...this._createExternalStyleSheetNgFactoryStubs(file, emitFlags),
...this._createNgSummaryStub(file, emitFlags)
];
}
private _createNgFactoryStub(file: NgAnalyzedFile, emitFlags: StubEmitFlags): GeneratedFile[] {
const generatedFiles: GeneratedFile[] = [];
const outputCtx = this._createOutputContext(
calculateGenFileName(ngfactoryFilePath(file.fileName, true), this.options.rootDir));
file.ngModules.forEach((ngModuleMeta, ngModuleIndex) => { file.ngModules.forEach((ngModuleMeta, ngModuleIndex) => {
// Note: the code below needs to executed for StubEmitFlags.Basic and StubEmitFlags.TypeCheck, // Note: the code below needs to executed for StubEmitFlags.Basic and StubEmitFlags.TypeCheck,
// so we don't change the .ngfactory file too much when adding the typecheck block. // so we don't change the .ngfactory file too much when adding the typecheck block.
@ -170,76 +237,9 @@ export class AotCompiler {
} }
}); });
// Make sure we create a .ngfactory if we have a injectable/directive/pipe/NgModule
// or a reference to a non source file.
// Note: This is overestimating the required .ngfactory files as the real calculation is harder.
// Only do this for StubEmitFlags.Basic, as adding a type check block
// does not change this file (as we generate type check blocks based on NgModules).
if (outputCtx.statements.length === 0 && (emitFlags & StubEmitFlags.Basic) &&
(file.directives.length || file.pipes.length || file.injectables.length ||
file.ngModules.length || file.exportsNonSourceFiles)) {
_createEmptyStub(outputCtx);
}
if (outputCtx.statements.length > 0) {
generatedFiles.push(this._codegenSourceModule(file.fileName, outputCtx));
}
return generatedFiles;
}
private _createExternalStyleSheetNgFactoryStubs(file: NgAnalyzedFile, emitFlags: StubEmitFlags):
GeneratedFile[] {
const generatedFiles: GeneratedFile[] = [];
if (!(emitFlags & StubEmitFlags.Basic)) {
// note: stylesheet stubs don't change when we produce type check stubs
return generatedFiles;
}
const fileSuffix = splitTypescriptSuffix(file.fileName, true)[1];
file.directives.forEach((dirSymbol) => {
const compMeta =
this._metadataResolver.getNonNormalizedDirectiveMetadata(dirSymbol) !.metadata;
if (!compMeta.isComponent) {
return;
}
// Note: compMeta is a component and therefore template is non null.
compMeta.template !.styleUrls.forEach((styleUrl) => {
const normalizedUrl = this._host.resourceNameToFileName(styleUrl, file.fileName);
if (!normalizedUrl) {
throw new Error(`Couldn't resolve resource ${styleUrl} relative to ${file.fileName}`);
}
const encapsulation =
compMeta.template !.encapsulation || this._config.defaultEncapsulation;
const outputCtx = this._createOutputContext(calculateGenFileName(
_stylesModuleUrl(
normalizedUrl, encapsulation === ViewEncapsulation.Emulated, fileSuffix),
this.options.rootDir));
_createEmptyStub(outputCtx);
generatedFiles.push(this._codegenSourceModule(normalizedUrl, outputCtx));
});
});
return generatedFiles;
}
private _createNgSummaryStub(file: NgAnalyzedFile, emitFlags: StubEmitFlags): GeneratedFile[] {
const generatedFiles: GeneratedFile[] = [];
// note: .ngsummary.js stubs don't change when we produce type check stubs
if (!this.options.enableSummariesForJit || !(emitFlags & StubEmitFlags.Basic)) {
return generatedFiles;
}
if (file.directives.length || file.injectables.length || file.ngModules.length ||
file.pipes.length || file.exportsNonSourceFiles) {
const outputCtx = this._createOutputContext(
calculateGenFileName(summaryForJitFileName(file.fileName, true), this.options.rootDir));
file.ngModules.forEach(ngModule => {
// create exports that user code can reference
createForJitStub(outputCtx, ngModule.type.reference);
});
if (outputCtx.statements.length === 0) { if (outputCtx.statements.length === 0) {
_createEmptyStub(outputCtx); _createEmptyStub(outputCtx);
} }
generatedFiles.push(this._codegenSourceModule(file.fileName, outputCtx));
}
return generatedFiles;
} }
private _createTypeCheckBlock( private _createTypeCheckBlock(
@ -298,8 +298,7 @@ export class AotCompiler {
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1]; const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
const generatedFiles: GeneratedFile[] = []; const generatedFiles: GeneratedFile[] = [];
const outputCtx = this._createOutputContext( const outputCtx = this._createOutputContext(ngfactoryFilePath(srcFileUrl, true));
calculateGenFileName(ngfactoryFilePath(srcFileUrl, true), this.options.rootDir));
generatedFiles.push( generatedFiles.push(
...this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables, outputCtx)); ...this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables, outputCtx));
@ -323,8 +322,15 @@ export class AotCompiler {
const componentStylesheet = this._styleCompiler.compileComponent(outputCtx, compMeta); const componentStylesheet = this._styleCompiler.compileComponent(outputCtx, compMeta);
// Note: compMeta is a component and therefore template is non null. // Note: compMeta is a component and therefore template is non null.
compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => { compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => {
// Note: fill non shim and shim style files as they might
// be shared by component with and without ViewEncapsulation.
const shim = this._styleCompiler.needsStyleShim(compMeta);
generatedFiles.push( generatedFiles.push(
this._codegenStyles(stylesheetMeta.moduleUrl !, compMeta, stylesheetMeta, fileSuffix)); this._codegenStyles(srcFileUrl, compMeta, stylesheetMeta, shim, fileSuffix));
if (this._options.allowEmptyCodegenFiles) {
generatedFiles.push(
this._codegenStyles(srcFileUrl, compMeta, stylesheetMeta, !shim, fileSuffix));
}
}); });
// compile components // compile components
@ -333,7 +339,7 @@ export class AotCompiler {
fileSuffix); fileSuffix);
this._compileComponentFactory(outputCtx, compMeta, ngModule, fileSuffix); this._compileComponentFactory(outputCtx, compMeta, ngModule, fileSuffix);
}); });
if (outputCtx.statements.length > 0) { if (outputCtx.statements.length > 0 || this._options.allowEmptyCodegenFiles) {
const srcModule = this._codegenSourceModule(srcFileUrl, outputCtx); const srcModule = this._codegenSourceModule(srcFileUrl, outputCtx);
generatedFiles.unshift(srcModule); generatedFiles.unshift(srcModule);
} }
@ -370,8 +376,7 @@ export class AotCompiler {
metadata: this._metadataResolver.getInjectableSummary(ref) !.type metadata: this._metadataResolver.getInjectableSummary(ref) !.type
})) }))
]; ];
const forJitOutputCtx = this._createOutputContext( const forJitOutputCtx = this._createOutputContext(summaryForJitFileName(srcFileName, true));
calculateGenFileName(summaryForJitFileName(srcFileName, true), this.options.rootDir));
const {json, exportAs} = serializeSummaries( const {json, exportAs} = serializeSummaries(
srcFileName, forJitOutputCtx, this._summaryResolver, this._symbolResolver, symbolSummaries, srcFileName, forJitOutputCtx, this._summaryResolver, this._symbolResolver, symbolSummaries,
typeData); typeData);
@ -382,7 +387,7 @@ export class AotCompiler {
])); ]));
}); });
const summaryJson = new GeneratedFile(srcFileName, summaryFileName(srcFileName), json); const summaryJson = new GeneratedFile(srcFileName, summaryFileName(srcFileName), json);
if (this.options.enableSummariesForJit) { if (this._options.enableSummariesForJit) {
return [summaryJson, this._codegenSourceModule(srcFileName, forJitOutputCtx)]; return [summaryJson, this._codegenSourceModule(srcFileName, forJitOutputCtx)];
} }
@ -392,18 +397,18 @@ export class AotCompiler {
private _compileModule(outputCtx: OutputContext, ngModule: CompileNgModuleMetadata): void { private _compileModule(outputCtx: OutputContext, ngModule: CompileNgModuleMetadata): void {
const providers: CompileProviderMetadata[] = []; const providers: CompileProviderMetadata[] = [];
if (this.options.locale) { if (this._options.locale) {
const normalizedLocale = this.options.locale.replace(/_/g, '-'); const normalizedLocale = this._options.locale.replace(/_/g, '-');
providers.push({ providers.push({
token: createTokenForExternalReference(this._reflector, Identifiers.LOCALE_ID), token: createTokenForExternalReference(this._reflector, Identifiers.LOCALE_ID),
useValue: normalizedLocale, useValue: normalizedLocale,
}); });
} }
if (this.options.i18nFormat) { if (this._options.i18nFormat) {
providers.push({ providers.push({
token: createTokenForExternalReference(this._reflector, Identifiers.TRANSLATIONS_FORMAT), token: createTokenForExternalReference(this._reflector, Identifiers.TRANSLATIONS_FORMAT),
useValue: this.options.i18nFormat useValue: this._options.i18nFormat
}); });
} }
@ -520,17 +525,13 @@ export class AotCompiler {
private _codegenStyles( private _codegenStyles(
srcFileUrl: string, compMeta: CompileDirectiveMetadata, srcFileUrl: string, compMeta: CompileDirectiveMetadata,
stylesheetMetadata: CompileStylesheetMetadata, fileSuffix: string): GeneratedFile { stylesheetMetadata: CompileStylesheetMetadata, isShimmed: boolean,
const outputCtx = this._createOutputContext(calculateGenFileName( fileSuffix: string): GeneratedFile {
_stylesModuleUrl( const outputCtx = this._createOutputContext(
stylesheetMetadata.moduleUrl !, this._styleCompiler.needsStyleShim(compMeta), _stylesModuleUrl(stylesheetMetadata.moduleUrl !, isShimmed, fileSuffix));
fileSuffix),
this.options.rootDir));
const compiledStylesheet = const compiledStylesheet =
this._styleCompiler.compileStyles(outputCtx, compMeta, stylesheetMetadata); this._styleCompiler.compileStyles(outputCtx, compMeta, stylesheetMetadata, isShimmed);
_resolveStyleStatements( _resolveStyleStatements(this._symbolResolver, compiledStylesheet, isShimmed, fileSuffix);
this._symbolResolver, compiledStylesheet, this._styleCompiler.needsStyleShim(compMeta),
fileSuffix);
return this._codegenSourceModule(srcFileUrl, outputCtx); return this._codegenSourceModule(srcFileUrl, outputCtx);
} }
@ -731,17 +732,3 @@ export function mergeAnalyzedFiles(analyzedFiles: NgAnalyzedFile[]): NgAnalyzedM
function mergeAndValidateNgFiles(files: NgAnalyzedFile[]): NgAnalyzedModules { function mergeAndValidateNgFiles(files: NgAnalyzedFile[]): NgAnalyzedModules {
return validateAnalyzedModules(mergeAnalyzedFiles(files)); return validateAnalyzedModules(mergeAnalyzedFiles(files));
} }
function calculateGenFileName(fileName: string, rootDir: string | undefined): string {
if (!rootDir) return fileName;
const fileNameParts = fileName.split(/\\|\//);
const rootDirParts = rootDir.split(/\\|\//);
if (!rootDirParts[rootDirParts.length - 1]) rootDirParts.pop();
let i = 0;
while (i < Math.min(fileNameParts.length, rootDirParts.length) &&
fileNameParts[i] === rootDirParts[i])
i++;
const result = [...rootDirParts, ...fileNameParts.slice(i)].join('/');
return result;
}

View File

@ -14,8 +14,9 @@ export interface AotCompilerOptions {
translations?: string; translations?: string;
missingTranslation?: MissingTranslationStrategy; missingTranslation?: MissingTranslationStrategy;
enableLegacyTemplate?: boolean; enableLegacyTemplate?: boolean;
/** TODO(tbosch): remove this flag as it is always on in the new ngc */
enableSummariesForJit?: boolean; enableSummariesForJit?: boolean;
preserveWhitespaces?: boolean; preserveWhitespaces?: boolean;
fullTemplateTypeCheck?: boolean; fullTemplateTypeCheck?: boolean;
rootDir?: string; allowEmptyCodegenFiles?: boolean;
} }

View File

@ -45,7 +45,7 @@ export interface StaticSymbolResolverHost {
* *
* See ImportResolver. * See ImportResolver.
*/ */
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string|null; fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
} }
const SUPPORTED_SCHEMA_VERSION = 3; const SUPPORTED_SCHEMA_VERSION = 3;
@ -163,7 +163,7 @@ export class StaticSymbolResolver {
/** /**
* Converts a file path to a module name that can be used as an `import`. * Converts a file path to a module name that can be used as an `import`.
*/ */
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string|null { fileNameToModuleName(importedFilePath: string, containingFilePath: string): string {
return this.knownFileNameToModuleNames.get(importedFilePath) || return this.knownFileNameToModuleNames.get(importedFilePath) ||
this.host.fileNameToModuleName(importedFilePath, containingFilePath); this.host.fileNameToModuleName(importedFilePath, containingFilePath);
} }
@ -292,11 +292,6 @@ export class StaticSymbolResolver {
this.resolvedFilePaths.add(filePath); this.resolvedFilePaths.add(filePath);
const resolvedSymbols: ResolvedStaticSymbol[] = []; const resolvedSymbols: ResolvedStaticSymbol[] = [];
const metadata = this.getModuleMetadata(filePath); const metadata = this.getModuleMetadata(filePath);
if (metadata['importAs']) {
// Index bundle indices should use the importAs module name defined
// in the bundle.
this.knownFileNameToModuleNames.set(filePath, metadata['importAs']);
}
if (metadata['metadata']) { if (metadata['metadata']) {
// handle direct declarations of the symbol // handle direct declarations of the symbol
const topLevelSymbolNames = const topLevelSymbolNames =

View File

@ -42,13 +42,14 @@ export class StyleCompiler {
styleUrls: template.styleUrls, styleUrls: template.styleUrls,
moduleUrl: identifierModuleUrl(comp.type) moduleUrl: identifierModuleUrl(comp.type)
}), }),
true); this.needsStyleShim(comp), true);
} }
compileStyles( compileStyles(
outputCtx: OutputContext, comp: CompileDirectiveMetadata, outputCtx: OutputContext, comp: CompileDirectiveMetadata,
stylesheet: CompileStylesheetMetadata): CompiledStylesheet { stylesheet: CompileStylesheetMetadata,
return this._compileStyles(outputCtx, comp, stylesheet, false); shim: boolean = this.needsStyleShim(comp)): CompiledStylesheet {
return this._compileStyles(outputCtx, comp, stylesheet, shim, false);
} }
needsStyleShim(comp: CompileDirectiveMetadata): boolean { needsStyleShim(comp: CompileDirectiveMetadata): boolean {
@ -57,8 +58,8 @@ export class StyleCompiler {
private _compileStyles( private _compileStyles(
outputCtx: OutputContext, comp: CompileDirectiveMetadata, outputCtx: OutputContext, comp: CompileDirectiveMetadata,
stylesheet: CompileStylesheetMetadata, isComponentStylesheet: boolean): CompiledStylesheet { stylesheet: CompileStylesheetMetadata, shim: boolean,
const shim = this.needsStyleShim(comp); isComponentStylesheet: boolean): CompiledStylesheet {
const styleExpressions: o.Expression[] = const styleExpressions: o.Expression[] =
stylesheet.styles.map(plainStyle => o.literal(this._shimIfNeeded(plainStyle, shim))); stylesheet.styles.map(plainStyle => o.literal(this._shimIfNeeded(plainStyle, shim)));
const dependencies: StylesCompileDependency[] = []; const dependencies: StylesCompileDependency[] = [];

View File

@ -410,7 +410,7 @@ export class MockAotCompilerHost implements AotCompilerHost {
fromSummaryFileName(filePath: string): string { return filePath; } fromSummaryFileName(filePath: string): string { return filePath; }
// AotCompilerHost // AotCompilerHost
fileNameToModuleName(importedFile: string, containingFile: string): string|null { fileNameToModuleName(importedFile: string, containingFile: string): string {
return importedFile.replace(EXT, ''); return importedFile.replace(EXT, '');
} }