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), **{
"angularCompilerOptions": {
"generateCodeForLibraries": False,
"allowEmptyCodegenFiles": True,
# FIXME: wrong place to de-dupe
"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();
// Strip leading at-signs, used to indicate a params file
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 expectedOuts = config['angularCompilerOptions']['expectedOut'];
const {basePath} = ng.calcProjectFileAndBasePath(project);
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);
}
@ -71,14 +68,28 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string
return filePath;
}
export function compile(
{fileLoader, compilerOpts, bazelOpts, files, expectedOuts, gatherDiagnostics}: {
fileLoader: FileLoader,
compilerOpts: ng.CompilerOptions,
bazelOpts: BazelOptions,
files: string[],
expectedOuts: string[], gatherDiagnostics?: (program: ng.Program) => ng.Diagnostics
}): {diagnostics: ng.Diagnostics, program: ng.Program} {
export function compile({allowNonHermeticReads, compilerOpts, tsHost, bazelOpts, files, inputs,
expectedOuts, gatherDiagnostics}: {
allowNonHermeticReads: boolean,
compilerOpts: ng.CompilerOptions,
tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
bazelOpts: BazelOptions,
files: string[],
expectedOuts: string[], gatherDiagnostics?: (program: ng.Program) => ng.Diagnostics
}): {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) {
compilerOpts.annotateForClosureCompiler = true;
compilerOpts.annotationsAs = 'static fields';
@ -89,7 +100,6 @@ export function compile(
}
const writtenExpectedOuts = [...expectedOuts];
const tsHost = ts.createCompilerHost(compilerOpts, true);
const originalWriteFile = tsHost.writeFile.bind(tsHost);
tsHost.writeFile =

View File

@ -39,7 +39,7 @@ export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext>
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;
@ -47,6 +47,23 @@ export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext>
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 {
if (!this.context.fileExists(filePath)) {
// 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)) {
const metadataPath = filePath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) {
return this.readMetadata(metadataPath, filePath);
} else {
let metadatas = this.readMetadata(filePath);
if (!metadatas) {
// 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
// to resolve symbols.
return [this.upgradeVersion1Metadata(
metadatas = [this.upgradeVersion1Metadata(
{'__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);
return metadata ? [metadata] : [];
}
readMetadata(filePath: string, dtsFilePath: string): ModuleMetadata[] {
let metadatas = this.resolverCache.get(filePath);
protected readMetadata(dtsFilePath: string): ModuleMetadata[]|undefined {
let metadatas = this.resolverCache.get(dtsFilePath);
if (metadatas) {
return metadatas;
}
const metadataPath = dtsFilePath.replace(DTS, '.metadata.json');
if (!this.context.fileExists(metadataPath)) {
return undefined;
}
try {
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
const metadataOrMetadatas = JSON.parse(this.context.readFile(metadataPath));
const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
[];
@ -87,10 +109,10 @@ export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext>
if (!v3Metadata && v1Metadata) {
metadatas.push(this.upgradeVersion1Metadata(v1Metadata, dtsFilePath));
}
this.resolverCache.set(filePath, metadatas);
this.resolverCache.set(dtsFilePath, metadatas);
return metadatas;
} catch (e) {
console.error(`Failed to read JSON file ${filePath}`);
console.error(`Failed to read JSON file ${metadataPath}`);
throw e;
}
}
@ -347,6 +369,11 @@ export class CompilerHost extends BaseAotCompilerHost<CompilerHostContext> {
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
*/
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
// assume it exists it so that the `resolve` method works!
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 LibrarySummary {
fileName: string;
text: string;
sourceFile?: ts.SourceFile;
}
export interface Program {
getTsProgram(): ts.Program;
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken): ts.Diagnostic[];
@ -110,7 +116,7 @@ export interface Program {
customTransformers?: CustomTransformers,
emitCallback?: TsEmitCallback
}): ts.EmitResult;
getLibrarySummaries(): {fileName: string, content: string}[];
getLibrarySummaries(): LibrarySummary[];
}
// Wrapper for createProgram.

View File

@ -126,11 +126,13 @@ export class PathMappedCompilerHost extends CompilerHost {
continue;
}
if (DTS.test(rootedPath)) {
const metadataPath = rootedPath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) {
return this.readMetadata(metadataPath, rootedPath);
const metadatas = this.readMetadata(rootedPath);
if (metadatas) {
return metadatas;
}
} 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);
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
// in Angular 5 and will be re-visited in Angular 6.
preserveWhitespaces?: boolean;
/** generate all possible generated files */
allowEmptyCodegenFiles?: boolean;
}
export interface CompilerHost extends ts.CompilerHost {
@ -203,6 +206,12 @@ export interface TsEmitArguments {
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
export interface LibrarySummary {
fileName: string;
text: string;
sourceFile?: ts.SourceFile;
}
export interface Program {
/**
* Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources.
@ -280,8 +289,9 @@ export interface Program {
}): ts.EmitResult;
/**
* Returns the .ngsummary.json files of libraries that have been compiled
* in this program or previous programs.
* Returns the .d.ts / .ngsummary.json / .ngfactory.d.ts files of libraries that have been emitted
* 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 {ModuleMetadata} from '../metadata/index';
import {CompilerHost, CompilerOptions} from './api';
import {CompilerHost, CompilerOptions, LibrarySummary} from './api';
import {GENERATED_FILES} from './util';
const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/;
@ -37,6 +37,11 @@ interface GenSourceFile {
emitCtx: EmitterVisitorContext;
}
export interface CodeGenerator {
generateFile(genFileName: string, baseFileName?: string): GeneratedFile;
findGeneratedFileNames(fileName: string): string[];
}
/**
* Implements the following hosts based on an api.CompilerHost:
* - ts.CompilerHost to be consumed by a ts.Program
@ -48,11 +53,12 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
TypeCheckHost {
private rootDirs: string[];
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 generatedSourceFiles = new Map<string, GenSourceFile>();
private generatedCodeFor = new Set<string>();
private generatedCodeFor = new Map<string, string[]>();
private emitter = new TypeScriptEmitter();
private librarySummaries = new Map<string, LibrarySummary>();
getCancellationToken: () => ts.CancellationToken;
getDefaultLibLocation: () => string;
trace: (s: string) => void;
@ -61,10 +67,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
constructor(
private rootFiles: string[], options: CompilerOptions, context: CompilerHost,
private metadataProvider: MetadataProvider,
private codeGenerator: (fileName: string) => GeneratedFile[],
private summariesFromPreviousCompilations: Map<string, string>) {
private metadataProvider: MetadataProvider, private codeGenerator: CodeGenerator,
librarySummaries: LibrarySummary[]) {
super(options, context);
librarySummaries.forEach(summary => this.librarySummaries.set(summary.fileName, summary));
this.moduleResolutionCache = ts.createModuleResolutionCache(
this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context));
const basePath = this.options.basePath !;
@ -168,6 +174,11 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
'fileNameToModuleName from containingFile', containingFile, 'to importedFile',
importedFile);
}
const importAs = this.getImportAs(importedFile);
if (importAs) {
return importAs;
}
// drop extension
importedFile = importedFile.replace(EXT, '');
const importedFilePackagName = getPackageName(importedFile);
@ -229,15 +240,18 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
private getOriginalSourceFile(
filePath: string, languageVersion?: ts.ScriptTarget,
onError?: ((message: string) => void)|undefined): ts.SourceFile|undefined {
let sf = this.originalSourceFiles.get(filePath);
if (sf) {
return sf;
onError?: ((message: string) => void)|undefined): ts.SourceFile|null {
// Note: we need the explicit check via `has` as we also cache results
// that were null / undefined.
if (this.originalSourceFiles.has(filePath)) {
return this.originalSourceFiles.get(filePath) !;
}
if (!languageVersion) {
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);
return sf;
}
@ -250,9 +264,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
return this.metadataProvider.getMetadata(sf);
}
updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile|null {
updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile {
if (!genFile.stmts) {
return null;
throw new Error(
`Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileUrl}`);
}
const oldGenFile = this.generatedSourceFiles.get(genFile.genFileUrl);
if (!oldGenFile) {
@ -271,10 +286,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
return this.addGeneratedFile(genFile, newRefs);
}
private addGeneratedFile(genFile: GeneratedFile, externalReferences: Set<string>): ts.SourceFile
|null {
private addGeneratedFile(genFile: GeneratedFile, externalReferences: Set<string>): ts.SourceFile {
if (!genFile.stmts) {
return null;
throw new Error(
`Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileUrl}`);
}
const {sourceText, context} = this.emitter.emitStatementsAndContext(
genFile.srcFileUrl, genFile.genFileUrl, genFile.stmts, /* preamble */ '',
@ -288,49 +303,95 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
return sf;
}
private ensureCodeGeneratedFor(fileName: string): void {
if (this.generatedCodeFor.has(fileName)) {
return;
shouldGenerateFile(fileName: string): {generate: boolean, baseFileName?: string} {
// TODO(tbosch): allow generating files that are not in the rootDir
// 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) {
return {generate: false};
}
const [, base, genSuffix, suffix] = genMatch;
if (suffix !== 'ts') {
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 baseNameFromGeneratedFile = this._getBaseNameForGeneratedSourceFile(fileName);
if (baseNameFromGeneratedFile) {
return this.ensureCodeGeneratedFor(baseNameFromGeneratedFile);
}
const sf = this.getOriginalSourceFile(fileName, this.options.target || ts.ScriptTarget.Latest);
if (!sf) {
return;
}
const genFileNames: string[] = [];
if (this.isSourceFile(fileName)) {
// Note: we can't exit early here,
// as we might need to clear out old changes to `SourceFile.referencedFiles`
// that were created by a previous run, given an original CompilerHost
// 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);
shouldGenerateFilesFor(fileName: string) {
// TODO(tbosch): allow generating files that are not in the rootDir
// See https://github.com/angular/angular/issues/19337
return !GENERATED_FILES.test(fileName) && this.isSourceFile(fileName) &&
(!this.options.rootDir || pathStartsWithPrefix(this.options.rootDir, fileName));
}
getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget,
onError?: ((message: string) => void)|undefined): ts.SourceFile {
this.ensureCodeGeneratedFor(fileName);
const genFile = this.generatedSourceFiles.get(fileName);
if (genFile) {
return genFile.sourceFile;
// Note: Don't exit early in this method to make sure
// we always have up to date references on the file!
let genFileNames: string[] = [];
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,
// 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 {
@ -344,48 +405,19 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
fileExists(fileName: string): boolean {
fileName = stripNgResourceSuffix(fileName);
if (fileName.endsWith('.ngfactory.d.ts')) {
// 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;
}
}
// Note: Don't rely on this.generatedSourceFiles here,
// as it might not have been filled yet.
if (this._getBaseNameForGeneratedSourceFile(fileName)) {
if (this.librarySummaries.has(fileName) || this.generatedSourceFiles.has(fileName)) {
return true;
}
return this.summariesFromPreviousCompilations.has(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));
if (this.shouldGenerateFile(fileName).generate) {
return true;
}
return this.originalFileExists(fileName);
}
loadSummary(filePath: string): string|null {
if (this.summariesFromPreviousCompilations.has(filePath)) {
return this.summariesFromPreviousCompilations.get(filePath) !;
const summary = this.librarySummaries.get(filePath);
if (summary) {
return summary.text;
}
return super.loadSummary(filePath);
}
@ -393,13 +425,19 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
isSourceFile(filePath: string): boolean {
// If we have a summary from a previous compilation,
// treat the file never as a source file.
if (this.summariesFromPreviousCompilations.has(summaryFileName(filePath))) {
if (this.librarySummaries.has(filePath)) {
return false;
}
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) =>
this.context.getDefaultLibFileName(options)
getCurrentDirectory = () => this.context.getCurrentDirectory();
@ -451,12 +489,19 @@ function getPackageName(filePath: string): string|null {
export function relativeToRootDirs(filePath: string, rootDirs: string[]): string {
if (!filePath) return filePath;
for (const dir of rootDirs || []) {
const rel = path.relative(dir, filePath);
if (rel.indexOf('.') != 0) return rel;
const rel = pathStartsWithPrefix(dir, filePath);
if (rel) {
return rel;
}
}
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 {
return filePath.replace(/.*node_modules\//, '');
}
@ -473,12 +518,3 @@ function stripNgResourceSuffix(fileName: string): string {
function addNgResourceSuffix(fileName: string): string {
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 {ModuleMetadata, createBundleIndexHost} from '../metadata/index';
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
import {TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from './util';
@ -35,10 +35,10 @@ const defaultEmitCallback: TsEmitCallback =
class AngularCompilerProgram implements Program {
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
private oldTsProgram: ts.Program|undefined;
private _emittedGenFiles: GeneratedFile[]|undefined;
private emittedLibrarySummaries: LibrarySummary[]|undefined;
// Lazily initialized fields
private _typeCheckHost: TypeCheckHost;
@ -59,8 +59,7 @@ class AngularCompilerProgram implements Program {
}
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
if (oldProgram) {
oldProgram.getLibrarySummaries().forEach(
({content, fileName}) => this.summariesFromPreviousCompilations.set(fileName, content));
this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries();
}
if (options.flatModuleOutFile) {
@ -82,21 +81,12 @@ class AngularCompilerProgram implements Program {
this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit);
}
getLibrarySummaries(): {fileName: string, content: string}[] {
const emittedLibSummaries: {fileName: string, content: string}[] = [];
this.summariesFromPreviousCompilations.forEach(
(content, fileName) => emittedLibSummaries.push({fileName, content}));
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 !});
}
});
getLibrarySummaries(): LibrarySummary[] {
const result = [...this.oldProgramLibrarySummaries];
if (this.emittedLibrarySummaries) {
result.push(...this.emittedLibrarySummaries);
}
return emittedLibSummaries;
return result;
}
getTsProgram(): ts.Program { return this.tsProgram; }
@ -132,8 +122,8 @@ class AngularCompilerProgram implements Program {
if (this._analyzedModules) {
throw new Error('Angular structure already loaded');
}
const {tmpProgram, analyzedFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
return this._compiler.loadFilesAsync(analyzedFiles)
const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
return this._compiler.loadFilesAsync(sourceFiles)
.catch(this.catchAnalysisError.bind(this))
.then(analyzedModules => {
if (this._analyzedModules) {
@ -159,7 +149,6 @@ class AngularCompilerProgram implements Program {
const bundle = this.compiler.emitMessageBundle(this.analyzedModules, locale);
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)) ===
0) {
return {emitSkipped: true, diagnostics: [], emittedFiles: []};
@ -172,6 +161,21 @@ class AngularCompilerProgram implements Program {
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
// a reference to the .d.ts file.
@ -183,14 +187,13 @@ class AngularCompilerProgram implements Program {
sourceFile.referencedFiles = originalReferences;
}
}
let emitResult: ts.EmitResult;
try {
emitResult = emitCallback({
program: this.tsProgram,
host: this.host,
options: this.options,
writeFile: createWriteFileCallback(genFiles, this.host, outSrcMapping),
writeFile: writeTsFile,
emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS,
customTransformers: this.calculateTransforms(genFiles, customTransformers)
});
@ -211,7 +214,8 @@ class AngularCompilerProgram implements Program {
if (emitFlags & EmitFlags.Codegen) {
genFiles.forEach(gf => {
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)) {
const metadata = this.metadataCache.getMetadata(sf);
const metadataText = JSON.stringify([metadata]);
this.host.writeFile(
srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json')), metadataText, false);
const outFileName = srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json'));
this.writeFile(outFileName, metadataText, false, undefined, undefined, [sf]);
}
});
}
@ -310,10 +314,10 @@ class AngularCompilerProgram implements Program {
if (this._analyzedModules) {
return;
}
const {tmpProgram, analyzedFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
let analyzedModules: NgAnalyzedModules;
const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
let analyzedModules: NgAnalyzedModules|null;
try {
analyzedModules = this._compiler.loadFilesSync(analyzedFiles);
analyzedModules = this._compiler.loadFilesSync(sourceFiles);
} catch (e) {
analyzedModules = this.catchAnalysisError(e);
}
@ -322,9 +326,9 @@ class AngularCompilerProgram implements Program {
private _createProgramWithBasicStubs(): {
tmpProgram: ts.Program,
analyzedFiles: NgAnalyzedFile[],
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter,
rootNames: string[],
sourceFiles: string[],
} {
if (this._analyzedModules) {
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!
const oldTsProgram = this.oldTsProgram;
this.oldTsProgram = undefined;
const analyzedFiles: NgAnalyzedFile[] = [];
const codegen = (fileName: string) => {
if (this._analyzedModules) {
throw new Error(`Internal Error: already initalized!`);
}
const analyzedFile = this._compiler.analyzeFile(fileName);
analyzedFiles.push(analyzedFile);
const debug = fileName.endsWith('application_ref.ts');
return this._compiler.emitBasicStubs(analyzedFile);
const codegen: CodeGenerator = {
generateFile: (genFileName, baseFileName) =>
this._compiler.emitBasicStub(genFileName, baseFileName),
findGeneratedFileNames: (fileName) => this._compiler.findGeneratedFileNames(fileName),
};
const hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter(
this.rootNames, this.options, this.host, this.metadataCache, codegen,
this.summariesFromPreviousCompilations);
this.oldProgramLibrarySummaries);
const aotOptions = getAotCompilerOptions(this.options);
this._compiler = createAotCompiler(hostAdapter, aotOptions).compiler;
this._typeCheckHost = hostAdapter;
@ -354,26 +355,41 @@ class AngularCompilerProgram implements Program {
this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn));
if (this.options.noResolve) {
this.rootNames.forEach(rootName => {
const sf =
hostAdapter.getSourceFile(rootName, this.options.target || ts.ScriptTarget.Latest);
sf.referencedFiles.forEach((fileRef) => {
if (GENERATED_FILES.test(fileRef.fileName)) {
rootNames.push(fileRef.fileName);
}
});
if (hostAdapter.shouldGenerateFilesFor(rootName)) {
rootNames.push(...this._compiler.findGeneratedFileNames(rootName));
}
});
}
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(
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules,
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules|null,
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) {
this._analyzedModules = analyzedModules;
const genFiles = this._compiler.emitTypeCheckStubs(analyzedModules);
genFiles.forEach(gf => hostAdapter.updateGeneratedFile(gf));
this._analyzedModules = analyzedModules || emptyModules;
if (analyzedModules) {
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);
// Note: the new ts program should be completely reusable by TypeScript as:
// - 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)) {
const parserErrors = getParseErrors(e);
if (parserErrors && parserErrors.length) {
@ -404,7 +420,7 @@ class AngularCompilerProgram implements Program {
code: DEFAULT_ERROR_CODE
}];
}
return emptyModules;
return null;
}
throw e;
}
@ -417,7 +433,7 @@ class AngularCompilerProgram implements Program {
if (!(emitFlags & EmitFlags.Codegen)) {
return {genFiles: [], genDiags: []};
}
const genFiles = this._emittedGenFiles = this.compiler.emitAllImpls(this.analyzedModules);
const genFiles = this.compiler.emitAllImpls(this.analyzedModules);
return {genFiles, genDiags: []};
} catch (e) {
// 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[]} {
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(
@ -483,31 +544,7 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
enableSummariesForJit: true,
preserveWhitespaces: options.preserveWhitespaces,
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
rootDir: options.rootDir,
};
}
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);
allowEmptyCodegenFiles: options.allowEmptyCodegenFiles,
};
}

View File

@ -713,17 +713,17 @@ describe('ngc transformer command-line', () => {
});
});
it('should be able to generate a flat module library', () => {
function writeFlatModule(outFile: string) {
writeConfig(`
{
"extends": "./tsconfig-base.json",
"angularCompilerOptions": {
"flatModuleId": "flat_module",
"flatModuleOutFile": "index.js",
"skipTemplateCodegen": true
},
"files": ["public-api.ts"]
}
{
"extends": "./tsconfig-base.json",
"angularCompilerOptions": {
"flatModuleId": "flat_module",
"flatModuleOutFile": "${outFile}",
"skipTemplateCodegen": true
},
"files": ["public-api.ts"]
}
`);
write('public-api.ts', `
export * from './src/flat.component';
@ -753,6 +753,10 @@ describe('ngc transformer command-line', () => {
})
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);
expect(exitCode).toEqual(0);
@ -760,6 +764,60 @@ describe('ngc transformer command-line', () => {
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', () => {
beforeEach(() => {
writeConfig();

View File

@ -10,7 +10,7 @@ import * as compiler from '@angular/compiler';
import * as ts from 'typescript';
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 {Directory, Entry, MockAotContext, MockCompilerHost} from '../mocks';
@ -21,9 +21,14 @@ const aGeneratedFile = new compiler.GeneratedFile(
const aGeneratedFileText = `var x:any = 1;\n`;
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 {
const context = new MockAotContext('/tmp/', files);
@ -37,16 +42,16 @@ describe('NgCompilerHost', () => {
moduleResolution: ts.ModuleResolutionKind.NodeJs,
},
ngHost = createNgHost({files}),
summariesFromPreviousCompilations = new Map<string, string>(),
librarySummaries = [],
}: {
files?: Directory,
options?: CompilerOptions,
ngHost?: CompilerHost,
summariesFromPreviousCompilations?: Map<string, string>
librarySummaries?: LibrarySummary[]
} = {}) {
return new TsCompilerAotCompilerTypeCheckHostAdapter(
['/tmp/index.ts'], options, ngHost, new MetadataCollector(), codeGenerator,
summariesFromPreviousCompilations);
librarySummaries);
}
describe('fileNameToModuleName', () => {
@ -180,7 +185,8 @@ describe('NgCompilerHost', () => {
});
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({
files: {
'tmp': {
@ -201,11 +207,13 @@ describe('NgCompilerHost', () => {
expect(genSf.text).toBe(aGeneratedFileText);
// 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', () => {
codeGenerator.and.returnValue([aGeneratedFile]);
codeGenerator.findGeneratedFileNames.and.returnValue(['/tmp/src/index.ngfactory.ts']);
codeGenerator.generateFile.and.returnValue(aGeneratedFile);
const host = createHost({
files: {
'tmp': {
@ -226,10 +234,13 @@ describe('NgCompilerHost', () => {
expect(sf.referencedFiles[1].fileName).toBe('/tmp/src/index.ngfactory.ts');
// 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', () => {
codeGenerator.findGeneratedFileNames.and.returnValue(['/tmp/src/index.ngfactory.ts']);
const ngHost = createNgHost();
const sfText = `
/// <reference path="main.ts"/>
@ -237,8 +248,9 @@ describe('NgCompilerHost', () => {
const sf = ts.createSourceFile('/tmp/src/index.ts', sfText, ts.ScriptTarget.Latest);
ngHost.getSourceFile = () => sf;
codeGenerator.and.returnValue(
[new compiler.GeneratedFile('/tmp/src/index.ts', '/tmp/src/index.ngfactory.ts', [])]);
codeGenerator.findGeneratedFileNames.and.returnValue(['/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});
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[1].fileName).toBe('/tmp/src/index.ngfactory.ts');
codeGenerator.and.returnValue([]);
codeGenerator.findGeneratedFileNames.and.returnValue([]);
codeGenerator.generateFile.and.returnValue(null);
const host2 = createHost({ngHost});
host2.getSourceFile('/tmp/src/index.ts', ts.ScriptTarget.Latest);
@ -257,7 +270,8 @@ describe('NgCompilerHost', () => {
describe('updateSourceFile', () => {
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': ''}}}});
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', () => {
codeGenerator.and.returnValue(
[new compiler.GeneratedFile('/tmp/src/index.ts', '/tmp/src/index.ngfactory.ts', [
new compiler.DeclareVarStmt(
'x', new compiler.ExternalExpr(new compiler.ExternalReference('aModule', 'aName')))
])]);
codeGenerator.findGeneratedFileNames.and.returnValue(['/tmp/src/index.ngfactory.ts']);
codeGenerator.generateFile.and.returnValue(new compiler.GeneratedFile(
'/tmp/src/index.ts', '/tmp/src/index.ngfactory.ts',
[new compiler.DeclareVarStmt(
'x',
new compiler.ExternalExpr(new compiler.ExternalReference('aModule', 'aName')))]));
const host = createHost({files: {'tmp': {'src': {'index.ts': ''}}}});
host.getSourceFile('/tmp/src/index.ngfactory.ts', ts.ScriptTarget.Latest);
@ -292,32 +307,4 @@ describe('NgCompilerHost', () => {
].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,41 +38,42 @@ describe('ng program', () => {
`;
}
function compileLib(libName: string) {
testSupport.writeFiles({
[`${libName}_src/index.ts`]: createModuleAndCompSource(libName),
});
const options = testSupport.createCompilerOptions();
const program = ng.createProgram({
rootNames: [path.resolve(testSupport.basePath, `${libName}_src/index.ts`)],
options,
host: ng.createCompilerHost({options}),
});
expectNoDiagnosticsInProgram(options, program);
fs.symlinkSync(
path.resolve(testSupport.basePath, 'built', `${libName}_src`),
path.resolve(testSupport.basePath, 'node_modules', libName));
program.emit({emitFlags: ng.EmitFlags.DTS | ng.EmitFlags.JS | ng.EmitFlags.Metadata});
}
function compile(
oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions,
rootNames?: string[]): ng.Program {
const options = testSupport.createCompilerOptions(overrideOptions);
if (!rootNames) {
rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')];
}
const program = ng.createProgram({
rootNames: rootNames,
options,
host: ng.createCompilerHost({options}), oldProgram,
});
expectNoDiagnosticsInProgram(options, program);
program.emit();
return program;
}
describe('reuse of old program', () => {
function compileLib(libName: string) {
testSupport.writeFiles({
[`${libName}_src/index.ts`]: createModuleAndCompSource(libName),
});
const options = testSupport.createCompilerOptions({
skipTemplateCodegen: true,
});
const program = ng.createProgram({
rootNames: [path.resolve(testSupport.basePath, `${libName}_src/index.ts`)],
options,
host: ng.createCompilerHost({options}),
});
expectNoDiagnosticsInProgram(options, program);
fs.symlinkSync(
path.resolve(testSupport.basePath, 'built', `${libName}_src`),
path.resolve(testSupport.basePath, 'node_modules', libName));
program.emit({emitFlags: ng.EmitFlags.DTS | ng.EmitFlags.JS | ng.EmitFlags.Metadata});
}
function compile(oldProgram?: ng.Program): ng.Program {
const options = testSupport.createCompilerOptions();
const rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')];
const program = ng.createProgram({
rootNames: rootNames,
options: testSupport.createCompilerOptions(),
host: ng.createCompilerHost({options}), oldProgram,
});
expectNoDiagnosticsInProgram(options, program);
program.emit();
return program;
}
it('should reuse generated code for libraries from old programs', () => {
compileLib('lib');
testSupport.writeFiles({
@ -123,6 +124,29 @@ describe('ng program', () => {
.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', () => {
testSupport.writeFiles({'src/index.ts': createModuleAndCompSource('main')});
// Note: the second compile drops factories for library files,
@ -223,13 +247,118 @@ describe('ng program', () => {
const allRootNames = preProgram.getSourceFiles().map(sf => sf.fileName);
// now do the actual test with noResolve
const options = testSupport.createCompilerOptions({noResolve: true});
const host = ng.createCompilerHost({options});
const program = ng.createProgram({rootNames: allRootNames, options, host});
expectNoDiagnosticsInProgram(options, program);
program.emit();
const program = compile(undefined, {noResolve: true}, allRootNames);
testSupport.shouldExist('built/src/main.ngfactory.js');
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 {
private _templateAstCache =
new Map<StaticSymbol, {template: TemplateAst[], pipes: CompilePipeSummary[]}>();
private _analyzedFiles = new Map<string, NgAnalyzedFile>();
constructor(
private _config: CompilerConfig, private options: AotCompilerOptions,
private _config: CompilerConfig, private _options: AotCompilerOptions,
private _host: AotCompilerHost, private _reflector: StaticReflector,
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
@ -76,23 +77,99 @@ export class AotCompiler {
.then(() => analyzeResult);
}
analyzeFile(fileName: string): NgAnalyzedFile {
return analyzeFile(this._host, this._symbolResolver, this._metadataResolver, fileName);
private _analyzeFile(fileName: string): NgAnalyzedFile {
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[] {
return this._emitStubs(file, StubEmitFlags.Basic);
findGeneratedFileNames(fileName: string): string[] {
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[] {
const generatedFiles: GeneratedFile[] = [];
files.files.forEach(
file => this._emitStubs(file, StubEmitFlags.TypeCheck)
.forEach(genFile => generatedFiles.push(genFile)));
return generatedFiles;
emitBasicStub(genFileName: string, originalFileName?: string): GeneratedFile {
const outputCtx = this._createOutputContext(genFileName);
if (genFileName.endsWith('.ngfactory.ts')) {
if (!originalFileName) {
throw new Error(
`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>[] = [];
files.forEach(
file => file.ngModules.forEach(
@ -102,7 +179,8 @@ export class AotCompiler {
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(
file => file.ngModules.forEach(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
@ -110,19 +188,8 @@ export class AotCompiler {
return mergeAndValidateNgFiles(files);
}
private _emitStubs(file: NgAnalyzedFile, emitFlags: StubEmitFlags): GeneratedFile[] {
return [
...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));
private _createNgFactoryStub(
outputCtx: OutputContext, file: NgAnalyzedFile, emitFlags: StubEmitFlags) {
file.ngModules.forEach((ngModuleMeta, ngModuleIndex) => {
// 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.
@ -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)) {
if (outputCtx.statements.length === 0) {
_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) {
_createEmptyStub(outputCtx);
}
generatedFiles.push(this._codegenSourceModule(file.fileName, outputCtx));
}
return generatedFiles;
}
private _createTypeCheckBlock(
@ -298,8 +298,7 @@ export class AotCompiler {
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
const generatedFiles: GeneratedFile[] = [];
const outputCtx = this._createOutputContext(
calculateGenFileName(ngfactoryFilePath(srcFileUrl, true), this.options.rootDir));
const outputCtx = this._createOutputContext(ngfactoryFilePath(srcFileUrl, true));
generatedFiles.push(
...this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables, outputCtx));
@ -323,8 +322,15 @@ export class AotCompiler {
const componentStylesheet = this._styleCompiler.compileComponent(outputCtx, compMeta);
// Note: compMeta is a component and therefore template is non null.
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(
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
@ -333,7 +339,7 @@ export class AotCompiler {
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);
generatedFiles.unshift(srcModule);
}
@ -370,8 +376,7 @@ export class AotCompiler {
metadata: this._metadataResolver.getInjectableSummary(ref) !.type
}))
];
const forJitOutputCtx = this._createOutputContext(
calculateGenFileName(summaryForJitFileName(srcFileName, true), this.options.rootDir));
const forJitOutputCtx = this._createOutputContext(summaryForJitFileName(srcFileName, true));
const {json, exportAs} = serializeSummaries(
srcFileName, forJitOutputCtx, this._summaryResolver, this._symbolResolver, symbolSummaries,
typeData);
@ -382,7 +387,7 @@ export class AotCompiler {
]));
});
const summaryJson = new GeneratedFile(srcFileName, summaryFileName(srcFileName), json);
if (this.options.enableSummariesForJit) {
if (this._options.enableSummariesForJit) {
return [summaryJson, this._codegenSourceModule(srcFileName, forJitOutputCtx)];
}
@ -392,18 +397,18 @@ export class AotCompiler {
private _compileModule(outputCtx: OutputContext, ngModule: CompileNgModuleMetadata): void {
const providers: CompileProviderMetadata[] = [];
if (this.options.locale) {
const normalizedLocale = this.options.locale.replace(/_/g, '-');
if (this._options.locale) {
const normalizedLocale = this._options.locale.replace(/_/g, '-');
providers.push({
token: createTokenForExternalReference(this._reflector, Identifiers.LOCALE_ID),
useValue: normalizedLocale,
});
}
if (this.options.i18nFormat) {
if (this._options.i18nFormat) {
providers.push({
token: createTokenForExternalReference(this._reflector, Identifiers.TRANSLATIONS_FORMAT),
useValue: this.options.i18nFormat
useValue: this._options.i18nFormat
});
}
@ -520,17 +525,13 @@ export class AotCompiler {
private _codegenStyles(
srcFileUrl: string, compMeta: CompileDirectiveMetadata,
stylesheetMetadata: CompileStylesheetMetadata, fileSuffix: string): GeneratedFile {
const outputCtx = this._createOutputContext(calculateGenFileName(
_stylesModuleUrl(
stylesheetMetadata.moduleUrl !, this._styleCompiler.needsStyleShim(compMeta),
fileSuffix),
this.options.rootDir));
stylesheetMetadata: CompileStylesheetMetadata, isShimmed: boolean,
fileSuffix: string): GeneratedFile {
const outputCtx = this._createOutputContext(
_stylesModuleUrl(stylesheetMetadata.moduleUrl !, isShimmed, fileSuffix));
const compiledStylesheet =
this._styleCompiler.compileStyles(outputCtx, compMeta, stylesheetMetadata);
_resolveStyleStatements(
this._symbolResolver, compiledStylesheet, this._styleCompiler.needsStyleShim(compMeta),
fileSuffix);
this._styleCompiler.compileStyles(outputCtx, compMeta, stylesheetMetadata, isShimmed);
_resolveStyleStatements(this._symbolResolver, compiledStylesheet, isShimmed, fileSuffix);
return this._codegenSourceModule(srcFileUrl, outputCtx);
}
@ -731,17 +732,3 @@ export function mergeAnalyzedFiles(analyzedFiles: NgAnalyzedFile[]): NgAnalyzedM
function mergeAndValidateNgFiles(files: NgAnalyzedFile[]): NgAnalyzedModules {
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;
missingTranslation?: MissingTranslationStrategy;
enableLegacyTemplate?: boolean;
/** TODO(tbosch): remove this flag as it is always on in the new ngc */
enableSummariesForJit?: boolean;
preserveWhitespaces?: boolean;
fullTemplateTypeCheck?: boolean;
rootDir?: string;
allowEmptyCodegenFiles?: boolean;
}

View File

@ -45,7 +45,7 @@ export interface StaticSymbolResolverHost {
*
* See ImportResolver.
*/
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string|null;
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
}
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`.
*/
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string|null {
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string {
return this.knownFileNameToModuleNames.get(importedFilePath) ||
this.host.fileNameToModuleName(importedFilePath, containingFilePath);
}
@ -292,11 +292,6 @@ export class StaticSymbolResolver {
this.resolvedFilePaths.add(filePath);
const resolvedSymbols: ResolvedStaticSymbol[] = [];
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']) {
// handle direct declarations of the symbol
const topLevelSymbolNames =

View File

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

View File

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