From 6b7cead0c526e2ca87b0e90b084da6bb8ae5ac1e Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Thu, 12 Oct 2017 10:32:21 -0700 Subject: [PATCH] Revert "Revert "perf(compiler): skip type check and emit in bazel in some cases. (#19646)"" This reverts commit 94a925a1b029a5f5e3f342583decb55c7a8fe47f. --- packages/bazel/src/ngc-wrapped/BUILD.bazel | 1 + packages/bazel/src/ngc-wrapped/emit_cache.ts | 117 +++++ .../bazel/src/ngc-wrapped/extract_i18n.ts | 2 +- packages/bazel/src/ngc-wrapped/index.ts | 104 +++-- packages/compiler-cli/src/codegen.ts | 4 +- packages/compiler-cli/src/main.ts | 28 +- packages/compiler-cli/src/ngtools_api2.ts | 3 +- packages/compiler-cli/src/perform_compile.ts | 2 +- packages/compiler-cli/src/perform_watch.ts | 4 +- packages/compiler-cli/src/transformers/api.ts | 62 ++- .../src/transformers/compiler_host.ts | 20 +- .../src/transformers/entry_points.ts | 1 + .../transformers/node_emitter_transform.ts | 5 +- .../compiler-cli/src/transformers/program.ts | 428 ++++++++++-------- .../compiler-cli/src/transformers/util.ts | 5 + .../test/transformers/program_spec.ts | 37 +- packages/compiler/src/aot/generated_file.ts | 8 +- packages/compiler/test/aot/compiler_spec.ts | 28 +- .../compiler/test/aot/jit_summaries_spec.ts | 24 +- packages/compiler/test/aot/regression_spec.ts | 2 +- packages/compiler/test/aot/test_util.ts | 8 +- 21 files changed, 596 insertions(+), 297 deletions(-) create mode 100644 packages/bazel/src/ngc-wrapped/emit_cache.ts diff --git a/packages/bazel/src/ngc-wrapped/BUILD.bazel b/packages/bazel/src/ngc-wrapped/BUILD.bazel index 8c2897d859..45b0340a8c 100644 --- a/packages/bazel/src/ngc-wrapped/BUILD.bazel +++ b/packages/bazel/src/ngc-wrapped/BUILD.bazel @@ -7,6 +7,7 @@ ts_library( name = "ngc_lib", srcs = [ "index.ts", + "emit_cache.ts", "extract_i18n.ts", ], deps = [ diff --git a/packages/bazel/src/ngc-wrapped/emit_cache.ts b/packages/bazel/src/ngc-wrapped/emit_cache.ts new file mode 100644 index 0000000000..96aa3364f7 --- /dev/null +++ b/packages/bazel/src/ngc-wrapped/emit_cache.ts @@ -0,0 +1,117 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as ng from '@angular/compiler-cli'; +import {CompilerHost, debug, fixUmdModuleDeclarations} from '@bazel/typescript'; +import * as tsickle from 'tsickle'; +import * as ts from 'typescript'; + +interface EmitCacheEntry { + emitResult: tsickle.EmitResult; + writtenFiles: Array<{fileName: string, content: string, sourceFiles?: ts.SourceFile[]}>; + generatedFile: ng.GeneratedFile; +} + +interface SourceFileWithEmitCache extends ts.SourceFile { + emitCache: Map; +} + +function getCache(sf: ts.SourceFile, genFileName?: string): EmitCacheEntry|undefined { + const emitCache = (sf as SourceFileWithEmitCache).emitCache; + return emitCache ? emitCache.get(genFileName || sf.fileName) : undefined; +} + +function setCache(sf: ts.SourceFile, entry: EmitCacheEntry) { + let emitCache = (sf as SourceFileWithEmitCache).emitCache; + if (!emitCache) { + emitCache = new Map(); + (sf as SourceFileWithEmitCache).emitCache = emitCache; + } + emitCache.set(entry.generatedFile ? entry.generatedFile.genFileName : sf.fileName, entry); +} + +export function getCachedGeneratedFile(sf: ts.SourceFile, genFileName: string): ng.GeneratedFile| + undefined { + const cacheEntry = getCache(sf, genFileName); + return cacheEntry ? cacheEntry.generatedFile : undefined; +} + +export function emitWithCache( + program: ng.Program, inputsChanged: boolean, targetFileNames: string[], + compilerOpts: ng.CompilerOptions, host: CompilerHost): tsickle.EmitResult { + const emitCallback: ng.EmitCallback = ({ + targetSourceFiles, + writeFile, + cancellationToken, + emitOnlyDtsFiles, + customTransformers = {} + }) => { + if (!targetSourceFiles) { + // Note: we know that we always have targetSourceFiles + // as we called `ng.Program.emit` with `targetFileNames`. + throw new Error('Unexpected state: no targetSourceFiles!'); + } + let cacheHits = 0; + const mergedEmitResult = tsickle.mergeEmitResults(targetSourceFiles.map(targetSourceFile => { + const targetGeneratedFile = program.getGeneratedFile(targetSourceFile.fileName); + const cacheSf = targetGeneratedFile ? + program.getTsProgram().getSourceFile(targetGeneratedFile.srcFileName) : + targetSourceFile; + const cacheEntry = getCache(cacheSf, targetGeneratedFile && targetGeneratedFile.genFileName); + if (cacheEntry) { + let useEmitCache = false; + if (targetGeneratedFile && !program.hasChanged(targetSourceFile.fileName)) { + // we emitted a GeneratedFile with the same content as before -> use the cache + useEmitCache = true; + } else if (!inputsChanged && !targetGeneratedFile) { + // this is an input and no inputs have changed -> use the cache + useEmitCache = true; + } + if (useEmitCache) { + cacheHits++; + cacheEntry.writtenFiles.forEach( + ({fileName, content, sourceFiles}) => writeFile( + fileName, content, /*writeByteOrderMark*/ false, /*onError*/ undefined, + sourceFiles)); + return cacheEntry.emitResult; + } + } + const writtenFiles: + Array<{fileName: string, content: string, sourceFiles?: ts.SourceFile[]}> = []; + const recordingWriteFile = + (fileName: string, content: string, writeByteOrderMark: boolean, + onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => { + writtenFiles.push({fileName, content, sourceFiles}); + writeFile(fileName, content, writeByteOrderMark, onError, sourceFiles); + }; + const emitResult = tsickle.emitWithTsickle( + program.getTsProgram(), host, host, compilerOpts, targetSourceFile, recordingWriteFile, + cancellationToken, emitOnlyDtsFiles, { + beforeTs: customTransformers.before, + afterTs: [ + ...(customTransformers.after || []), + fixUmdModuleDeclarations((sf: ts.SourceFile) => host.amdModuleName(sf)), + ], + }); + setCache(cacheSf, { + emitResult, + writtenFiles, + generatedFile: targetGeneratedFile, + }); + return emitResult; + })); + debug(`Emitted ${targetSourceFiles.length} files with ${cacheHits} cache hits`); + return mergedEmitResult; + }; + return program + .emit({ + targetFileNames, + emitCallback, + emitFlags: ng.EmitFlags.DTS | ng.EmitFlags.JS | ng.EmitFlags.Codegen + }) as tsickle.EmitResult; +} diff --git a/packages/bazel/src/ngc-wrapped/extract_i18n.ts b/packages/bazel/src/ngc-wrapped/extract_i18n.ts index 301d7a32bc..c338ebe596 100644 --- a/packages/bazel/src/ngc-wrapped/extract_i18n.ts +++ b/packages/bazel/src/ngc-wrapped/extract_i18n.ts @@ -11,6 +11,6 @@ // Entry point if (require.main === module) { const args = process.argv.slice(2); - console.error('>>> now yet implemented!'); + console.error('>>> not yet implemented!'); process.exitCode = 1; } diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts index 0d3a2dceaa..979b53228a 100644 --- a/packages/bazel/src/ngc-wrapped/index.ts +++ b/packages/bazel/src/ngc-wrapped/index.ts @@ -8,12 +8,14 @@ // TODO(tbosch): figure out why we need this as it breaks node code within ngc-wrapped /// import * as ng from '@angular/compiler-cli'; -import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, fixUmdModuleDeclarations, parseTsconfig, runAsWorker, runWorkerLoop} from '@bazel/typescript'; +import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, parseTsconfig, runAsWorker, runWorkerLoop} from '@bazel/typescript'; import * as fs from 'fs'; import * as path from 'path'; import * as tsickle from 'tsickle'; import * as ts from 'typescript'; +import {emitWithCache, getCachedGeneratedFile} from './emit_cache'; + const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; const NGC_GEN_FILES = /^(.*?)\.(ngfactory|ngsummary|ngstyle|shim\.ngstyle)(.*)$/; // FIXME: we should be able to add the assets to the tsconfig so FileLoader @@ -74,23 +76,32 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string } export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, compilerOpts, - tsHost, bazelOpts, files, inputs, expectedOuts, gatherDiagnostics}: { + tsHost, bazelOpts, files, inputs, expectedOuts, + gatherDiagnostics = defaultGatherDiagnostics}: { allowNonHermeticReads: boolean, allDepsCompiledWithBazel?: boolean, compilerOpts: ng.CompilerOptions, tsHost: ts.CompilerHost, inputs?: {[path: string]: string}, bazelOpts: BazelOptions, files: string[], - expectedOuts: string[], gatherDiagnostics?: (program: ng.Program) => ng.Diagnostics + expectedOuts: string[], + gatherDiagnostics?: (program: ng.Program, inputsToCheck: ts.SourceFile[], + genFilesToCheck: ng.GeneratedFile[]) => ng.Diagnostics }): {diagnostics: ng.Diagnostics, program: ng.Program} { let fileLoader: FileLoader; + const oldFiles = new Map(); if (inputs) { fileLoader = new CachedFileLoader(fileCache, allowNonHermeticReads); // 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]; + const resolvedKey = path.resolve(key); + resolvedInputs[resolvedKey] = inputs[key]; + const cachedSf = fileCache.getCache(resolvedKey); + if (cachedSf) { + oldFiles.set(resolvedKey, cachedSf); + } } fileCache.updateCache(resolvedInputs); } else { @@ -178,40 +189,56 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, path.resolve(bazelBin, fileName) + '.d.ts'; } - const emitCallback: ng.TsEmitCallback = ({ - program, - targetSourceFile, - writeFile, - cancellationToken, - emitOnlyDtsFiles, - customTransformers = {}, - }) => - tsickle.emitWithTsickle( - program, bazelHost, bazelHost, compilerOpts, targetSourceFile, writeFile, - cancellationToken, emitOnlyDtsFiles, { - beforeTs: customTransformers.before, - afterTs: [ - ...(customTransformers.after || []), - fixUmdModuleDeclarations((sf: ts.SourceFile) => bazelHost.amdModuleName(sf)), - ], - }); + const oldProgram = { + getSourceFile: (fileName: string) => { return oldFiles.get(fileName); }, + getGeneratedFile: (srcFileName: string, genFileName: string) => { + const sf = oldFiles.get(srcFileName); + return sf ? getCachedGeneratedFile(sf, genFileName) : undefined; + }, + }; + const program = + ng.createProgram({rootNames: files, host: ngHost, options: compilerOpts, oldProgram}); + let inputsChanged = files.some(fileName => program.hasChanged(fileName)); - if (!gatherDiagnostics) { - gatherDiagnostics = (program) => - gatherDiagnosticsForInputsOnly(compilerOpts, bazelOpts, program); + let genFilesToCheck: ng.GeneratedFile[]; + let inputsToCheck: ts.SourceFile[]; + if (inputsChanged) { + // if an input file changed, we need to type check all + // of our compilation sources as well as all generated files. + inputsToCheck = bazelOpts.compilationTargetSrc.map( + fileName => program.getTsProgram().getSourceFile(fileName)); + genFilesToCheck = program.getGeneratedFiles().filter(gf => gf.genFileName.endsWith('.ts')); + } else { + // if no input file changed, only type check the changed generated files + // as these don't influence each other nor the type check of the input files. + inputsToCheck = []; + genFilesToCheck = program.getGeneratedFiles().filter( + gf => program.hasChanged(gf.genFileName) && gf.genFileName.endsWith('.ts')); + } + + debug( + `TypeChecking ${inputsToCheck ? inputsToCheck.length : 'all'} inputs and ${genFilesToCheck ? genFilesToCheck.length : 'all'} generated files`); + const diagnostics = [...gatherDiagnostics(program !, inputsToCheck, genFilesToCheck)]; + let emitResult: tsickle.EmitResult|undefined; + if (!diagnostics.length) { + const targetFileNames = [...bazelOpts.compilationTargetSrc]; + for (const genFile of program.getGeneratedFiles()) { + if (genFile.genFileName.endsWith('.ts')) { + targetFileNames.push(genFile.genFileName); + } + } + emitResult = emitWithCache(program, inputsChanged, targetFileNames, compilerOpts, bazelHost); + diagnostics.push(...emitResult.diagnostics); } - const {diagnostics, emitResult, program} = ng.performCompilation( - {rootNames: files, options: compilerOpts, host: ngHost, emitCallback, gatherDiagnostics}); - const tsickleEmitResult = emitResult as tsickle.EmitResult; let externs = '/** @externs */\n'; if (diagnostics.length) { console.error(ng.formatDiagnostics(compilerOpts, diagnostics)); - } else { + } else if (emitResult) { if (bazelOpts.tsickleGenerateExterns) { - externs += tsickle.getGeneratedExterns(tsickleEmitResult.externs); + externs += tsickle.getGeneratedExterns(emitResult.externs); } if (bazelOpts.manifest) { - const manifest = constructManifest(tsickleEmitResult.modulesManifest, bazelHost); + const manifest = constructManifest(emitResult.modulesManifest, bazelHost); fs.writeFileSync(bazelOpts.manifest, manifest); } } @@ -230,14 +257,9 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, return {program, diagnostics}; } -function isCompilationTarget(bazelOpts: BazelOptions, sf: ts.SourceFile): boolean { - return !NGC_GEN_FILES.test(sf.fileName) && - (bazelOpts.compilationTargetSrc.indexOf(sf.fileName) !== -1); -} - -function gatherDiagnosticsForInputsOnly( - options: ng.CompilerOptions, bazelOpts: BazelOptions, - ngProgram: ng.Program): (ng.Diagnostic | ts.Diagnostic)[] { +function defaultGatherDiagnostics( + ngProgram: ng.Program, inputsToCheck: ts.SourceFile[], + genFilesToCheck: ng.GeneratedFile[]): (ng.Diagnostic | ts.Diagnostic)[] { const tsProgram = ngProgram.getTsProgram(); const diagnostics: (ng.Diagnostic | ts.Diagnostic)[] = []; // These checks mirror ts.getPreEmitDiagnostics, with the important @@ -245,7 +267,7 @@ function gatherDiagnosticsForInputsOnly( // program.getDeclarationDiagnostics() it somehow corrupts the emit. diagnostics.push(...tsProgram.getOptionsDiagnostics()); diagnostics.push(...tsProgram.getGlobalDiagnostics()); - for (const sf of tsProgram.getSourceFiles().filter(f => isCompilationTarget(bazelOpts, f))) { + for (const sf of inputsToCheck) { // Note: We only get the diagnostics for individual files // to e.g. not check libraries. diagnostics.push(...tsProgram.getSyntacticDiagnostics(sf)); @@ -255,7 +277,9 @@ function gatherDiagnosticsForInputsOnly( // only gather the angular diagnostics if we have no diagnostics // in any other files. diagnostics.push(...ngProgram.getNgStructuralDiagnostics()); - diagnostics.push(...ngProgram.getNgSemanticDiagnostics()); + for (const genFile of genFilesToCheck) { + diagnostics.push(...ngProgram.getNgSemanticDiagnostics(genFile)); + } } return diagnostics; } diff --git a/packages/compiler-cli/src/codegen.ts b/packages/compiler-cli/src/codegen.ts index 5b00a9b412..d7bba9d3f9 100644 --- a/packages/compiler-cli/src/codegen.ts +++ b/packages/compiler-cli/src/codegen.ts @@ -58,8 +58,8 @@ export class CodeGenerator { private emit(analyzedModules: compiler.NgAnalyzedModules) { const generatedModules = this.compiler.emitAllImpls(analyzedModules); return generatedModules.map(generatedModule => { - const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl); - const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl); + const sourceFile = this.program.getSourceFile(generatedModule.srcFileName); + const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileName); const source = generatedModule.source || compiler.toTypeScript(generatedModule, PREAMBLE); this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]); return emitPath; diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts index b34cf29596..f2171f9244 100644 --- a/packages/compiler-cli/src/main.ts +++ b/packages/compiler-cli/src/main.ts @@ -39,7 +39,7 @@ export function main( return reportErrorsAndExit(options, compileDiags, consoleError); } -function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined { +function createEmitCallback(options: api.CompilerOptions): api.EmitCallback|undefined { const transformDecorators = options.annotationsAs !== 'decorators'; const transformTypesToClosure = options.annotateForClosureCompiler; if (!transformDecorators && !transformTypesToClosure) { @@ -58,20 +58,30 @@ function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|un return ({ program, - targetSourceFile, + targetSourceFiles, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers = {}, host, options - }) => - tsickle.emitWithTsickle( - program, tsickleHost, host, options, targetSourceFile, writeFile, - cancellationToken, emitOnlyDtsFiles, { - beforeTs: customTransformers.before, - afterTs: customTransformers.after, - }); + }) => { + if (targetSourceFiles) { + return tsickle.mergeEmitResults(targetSourceFiles.map( + targetSourceFile => tsickle.emitWithTsickle( + program, tsickleHost, host, options, targetSourceFile, writeFile, cancellationToken, + emitOnlyDtsFiles, { + beforeTs: customTransformers.before, + afterTs: customTransformers.after, + }))); + } + return tsickle.emitWithTsickle( + program, tsickleHost, host, options, /*targetSourceFile*/ undefined, writeFile, + cancellationToken, emitOnlyDtsFiles, { + beforeTs: customTransformers.before, + afterTs: customTransformers.after, + }); + }; } export interface NgcParsedConfiguration extends ParsedConfiguration { watch?: boolean; } diff --git a/packages/compiler-cli/src/ngtools_api2.ts b/packages/compiler-cli/src/ngtools_api2.ts index d4c1c0ab2f..fe37d7d82d 100644 --- a/packages/compiler-cli/src/ngtools_api2.ts +++ b/packages/compiler-cli/src/ngtools_api2.ts @@ -103,8 +103,7 @@ export interface Program { getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[]; getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken): ts.Diagnostic[]; - getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken): - Diagnostic[]; + getNgSemanticDiagnostics(genFile?: any, cancellationToken?: ts.CancellationToken): Diagnostic[]; loadNgStructureAsync(): Promise; emit({emitFlags, cancellationToken, customTransformers, emitCallback}: { emitFlags?: EmitFlags, diff --git a/packages/compiler-cli/src/perform_compile.ts b/packages/compiler-cli/src/perform_compile.ts index 0bfcf98fbb..40eeca8eef 100644 --- a/packages/compiler-cli/src/perform_compile.ts +++ b/packages/compiler-cli/src/perform_compile.ts @@ -144,7 +144,7 @@ export function performCompilation({rootNames, options, host, oldProgram, emitCa options: api.CompilerOptions, host?: api.CompilerHost, oldProgram?: api.Program, - emitCallback?: api.TsEmitCallback, + emitCallback?: api.EmitCallback, gatherDiagnostics?: (program: api.Program) => Diagnostics, customTransformers?: api.CustomTransformers, emitFlags?: api.EmitFlags diff --git a/packages/compiler-cli/src/perform_watch.ts b/packages/compiler-cli/src/perform_watch.ts index c5acc570f0..cef42f32ab 100644 --- a/packages/compiler-cli/src/perform_watch.ts +++ b/packages/compiler-cli/src/perform_watch.ts @@ -40,7 +40,7 @@ export interface PerformWatchHost { reportDiagnostics(diagnostics: Diagnostics): void; readConfiguration(): ParsedConfiguration; createCompilerHost(options: api.CompilerOptions): api.CompilerHost; - createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined; + createEmitCallback(options: api.CompilerOptions): api.EmitCallback|undefined; onFileChange( options: api.CompilerOptions, listener: (event: FileChangeEvent, fileName: string) => void, ready: () => void): {close: () => void}; @@ -51,7 +51,7 @@ export interface PerformWatchHost { export function createPerformWatchHost( configFileName: string, reportDiagnostics: (diagnostics: Diagnostics) => void, existingOptions?: ts.CompilerOptions, createEmitCallback?: (options: api.CompilerOptions) => - api.TsEmitCallback | undefined): PerformWatchHost { + api.EmitCallback | undefined): PerformWatchHost { return { reportDiagnostics: reportDiagnostics, createCompilerHost: options => createCompilerHost({options}), diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts index 2bdd727dd0..24e4948691 100644 --- a/packages/compiler-cli/src/transformers/api.ts +++ b/packages/compiler-cli/src/transformers/api.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {GeneratedFile, ParseSourceSpan} from '@angular/compiler'; +import {GeneratedFile as GeneratedFileImpl, ParseSourceSpan} from '@angular/compiler'; import * as ts from 'typescript'; export const DEFAULT_ERROR_CODE = 100; @@ -207,18 +207,32 @@ export interface CustomTransformers { afterTs?: ts.TransformerFactory[]; } -export interface TsEmitArguments { +export interface EmitArguments { program: ts.Program; host: CompilerHost; options: CompilerOptions; - targetSourceFile?: ts.SourceFile; + targetSourceFiles?: ts.SourceFile[]; writeFile?: ts.WriteFileCallback; cancellationToken?: ts.CancellationToken; emitOnlyDtsFiles?: boolean; customTransformers?: ts.CustomTransformers; } -export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; } +export interface EmitCallback { (args: EmitArguments): ts.EmitResult; } + +/** + * Represents a generated file that has not yet been emitted. + */ +export interface GeneratedFile { + /** + * The file name of the sourceFile from which this file was generated. + */ + srcFileName: string; + /** + * The file name of the generated but not yet emitted file. + */ + genFileName: string; +} /** * @internal @@ -282,7 +296,7 @@ export interface Program { * * Angular structural information is required to produce these diagnostics. */ - getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken): + getNgSemanticDiagnostics(genFile?: GeneratedFile, cancellationToken?: ts.CancellationToken): Diagnostic[]; /** @@ -293,16 +307,33 @@ export interface Program { */ loadNgStructureAsync(): Promise; + /** + * Retrieves the GeneratedFile with the given fileName. + */ + getGeneratedFile(genFileName: string): GeneratedFile|undefined; + + /** + * Retriesves all GeneratedFiles. + */ + getGeneratedFiles(): GeneratedFile[]; + + /** + * Calculates whether the given file has changed since the `oldProgram` that was passed + * to `createProgram`. + */ + hasChanged(fileName: string): boolean; + /** * Emit the files requested by emitFlags implied by the program. * * Angular structural information is required to emit files. */ - emit({emitFlags, cancellationToken, customTransformers, emitCallback}?: { + emit({targetFileNames, emitFlags, cancellationToken, customTransformers, emitCallback}?: { + targetFileNames?: string[], emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken, customTransformers?: CustomTransformers, - emitCallback?: TsEmitCallback + emitCallback?: EmitCallback }): ts.EmitResult; /** @@ -317,10 +348,25 @@ export interface Program { /** * @internal */ - getEmittedGeneratedFiles(): Map; + getEmittedGeneratedFiles(): Map; /** * @internal */ getEmittedSourceFiles(): Map; } + +export type OldProgram = Program | CachedFiles; + +export interface CachedFiles { + getSourceFile(fileName: string): ts.SourceFile|undefined; + getGeneratedFile(srcFileName: string, genFileName: string): GeneratedFile|undefined; +} + +export interface CreateProgram { + ({rootNames, options, host, oldProgram}: { + rootNames: string[], + options: CompilerOptions, + host: CompilerHost, oldProgram?: OldProgram + }): Program; +} diff --git a/packages/compiler-cli/src/transformers/compiler_host.ts b/packages/compiler-cli/src/transformers/compiler_host.ts index 8bc19ec2d4..ccc8248fba 100644 --- a/packages/compiler-cli/src/transformers/compiler_host.ts +++ b/packages/compiler-cli/src/transformers/compiler_host.ts @@ -15,11 +15,10 @@ import {TypeCheckHost} from '../diagnostics/translate_diagnostics'; import {ModuleMetadata} from '../metadata/index'; import {CompilerHost, CompilerOptions, LibrarySummary} from './api'; -import {GENERATED_FILES} from './util'; +import {EXT, GENERATED_FILES} from './util'; const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/; const DTS = /\.d\.ts$/; -const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; export function createCompilerHost( {options, tsHost = ts.createCompilerHost(options, true)}: @@ -262,11 +261,12 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile { if (!genFile.stmts) { throw new Error( - `Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileUrl}`); + `Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileName}`); } - const oldGenFile = this.generatedSourceFiles.get(genFile.genFileUrl); + const oldGenFile = this.generatedSourceFiles.get(genFile.genFileName); if (!oldGenFile) { - throw new Error(`Illegal State: previous GeneratedFile not found for ${genFile.genFileUrl}.`); + throw new Error( + `Illegal State: previous GeneratedFile not found for ${genFile.genFileName}.`); } const newRefs = genFileExternalReferences(genFile); const oldRefs = oldGenFile.externalReferences; @@ -276,7 +276,7 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends } if (!refsAreEqual) { throw new Error( - `Illegal State: external references changed in ${genFile.genFileUrl}.\nOld: ${Array.from(oldRefs)}.\nNew: ${Array.from(newRefs)}`); + `Illegal State: external references changed in ${genFile.genFileName}.\nOld: ${Array.from(oldRefs)}.\nNew: ${Array.from(newRefs)}`); } return this.addGeneratedFile(genFile, newRefs); } @@ -284,14 +284,14 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends private addGeneratedFile(genFile: GeneratedFile, externalReferences: Set): ts.SourceFile { if (!genFile.stmts) { throw new Error( - `Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileUrl}`); + `Invalid Argument: Expected a GenerateFile with statements. ${genFile.genFileName}`); } const {sourceText, context} = this.emitter.emitStatementsAndContext( - genFile.genFileUrl, genFile.stmts, /* preamble */ '', + genFile.genFileName, genFile.stmts, /* preamble */ '', /* emitSourceMaps */ false); const sf = ts.createSourceFile( - genFile.genFileUrl, sourceText, this.options.target || ts.ScriptTarget.Latest); - this.generatedSourceFiles.set(genFile.genFileUrl, { + genFile.genFileName, sourceText, this.options.target || ts.ScriptTarget.Latest); + this.generatedSourceFiles.set(genFile.genFileName, { sourceFile: sf, emitCtx: context, externalReferences, }); diff --git a/packages/compiler-cli/src/transformers/entry_points.ts b/packages/compiler-cli/src/transformers/entry_points.ts index 9a574fa586..1f57bc27a3 100644 --- a/packages/compiler-cli/src/transformers/entry_points.ts +++ b/packages/compiler-cli/src/transformers/entry_points.ts @@ -12,3 +12,4 @@ import {CompilerHost, CompilerOptions, Program} from './api'; export {createCompilerHost} from './compiler_host'; export {createProgram} from './program'; +export {isGeneratedFile} from './util'; diff --git a/packages/compiler-cli/src/transformers/node_emitter_transform.ts b/packages/compiler-cli/src/transformers/node_emitter_transform.ts index 0153e90848..bc2795cca0 100644 --- a/packages/compiler-cli/src/transformers/node_emitter_transform.ts +++ b/packages/compiler-cli/src/transformers/node_emitter_transform.ts @@ -9,6 +9,7 @@ import {GeneratedFile} from '@angular/compiler'; import * as ts from 'typescript'; +import {Program} from './api'; import {TypeScriptNodeEmitter} from './node_emitter'; import {GENERATED_FILES} from './util'; @@ -19,12 +20,12 @@ const PREAMBLE = `/** * tslint:disable */`; -export function getAngularEmitterTransformFactory(generatedFiles: Map): () => +export function getAngularEmitterTransformFactory(program: Program): () => (sourceFile: ts.SourceFile) => ts.SourceFile { return function() { const emitter = new TypeScriptNodeEmitter(); return function(sourceFile: ts.SourceFile): ts.SourceFile { - const g = generatedFiles.get(sourceFile.fileName); + const g = program.getGeneratedFile(sourceFile.fileName) as GeneratedFile; if (g && g.stmts) { const [newSourceFile] = emitter.updateSourceFile(sourceFile, g.stmts, PREAMBLE); return newSourceFile; diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index a8d2303ded..c7dbdedf9c 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -14,11 +14,12 @@ 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, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api'; +import {CachedFiles, CompilerHost, CompilerOptions, CreateProgram, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitArguments, EmitCallback, EmitFlags, LibrarySummary, OldProgram, Program, SOURCE} 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, createMessageDiagnostic, tsStructureIsReused} from './util'; +import {EXT, GENERATED_FILES, StructureIsReused, createMessageDiagnostic, tsStructureIsReused} from './util'; + /** * Maximum number of files that are emitable via calling ts.Program.emit @@ -32,17 +33,34 @@ const emptyModules: NgAnalyzedModules = { files: [] }; -const defaultEmitCallback: TsEmitCallback = - ({program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, - customTransformers}) => - program.emit( - targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); +const defaultEmitCallback: EmitCallback = + ({program, targetSourceFiles, writeFile, cancellationToken, emitOnlyDtsFiles, + customTransformers}) => { + if (targetSourceFiles) { + const diagnostics: ts.Diagnostic[] = []; + let emitSkipped = false; + const emittedFiles: string[] = []; + for (const targetSourceFile of targetSourceFiles) { + const er = program.emit( + targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + diagnostics.push(...er.diagnostics); + emitSkipped = emitSkipped || er.emitSkipped; + emittedFiles.push(...er.emittedFiles); + } + return {diagnostics, emitSkipped, emittedFiles}; + } else { + return program.emit( + /*targetSourceFile*/ undefined, writeFile, cancellationToken, emitOnlyDtsFiles, + customTransformers); + } + }; class AngularCompilerProgram implements Program { private metadataCache: LowerMetadataCache; private oldProgramLibrarySummaries: Map|undefined; private oldProgramEmittedGeneratedFiles: Map|undefined; private oldProgramEmittedSourceFiles: Map|undefined; + private oldProgramCachedFiles: CachedFiles|undefined; // Note: This will be cleared out as soon as we create the _tsProgram private oldTsProgram: ts.Program|undefined; private emittedLibrarySummaries: LibrarySummary[]|undefined; @@ -56,20 +74,33 @@ class AngularCompilerProgram implements Program { private _analyzedModules: NgAnalyzedModules|undefined; private _structuralDiagnostics: Diagnostic[]|undefined; private _programWithStubs: ts.Program|undefined; + private _generatedFilesMap: Map|undefined; + private _generatedFiles: GeneratedFile[]|undefined; + private _changedFileNames = new Map(); private _optionsDiagnostics: Diagnostic[] = []; constructor( private rootNames: string[], private options: CompilerOptions, private host: CompilerHost, - private oldProgram?: Program) { + oldProgram?: OldProgram) { const [major, minor] = ts.version.split('.'); if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 4)) { throw new Error('The Angular Compiler requires TypeScript >= 2.4.'); } - this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined; if (oldProgram) { - this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries(); - this.oldProgramEmittedGeneratedFiles = oldProgram.getEmittedGeneratedFiles(); - this.oldProgramEmittedSourceFiles = oldProgram.getEmittedSourceFiles(); + if ((oldProgram as Program).getTsProgram) { + const oldNgProgram = oldProgram as Program; + this.oldTsProgram = oldNgProgram.getTsProgram(); + this.oldProgramLibrarySummaries = oldNgProgram.getLibrarySummaries(); + this.oldProgramEmittedGeneratedFiles = oldNgProgram.getEmittedGeneratedFiles(); + this.oldProgramEmittedSourceFiles = oldNgProgram.getEmittedSourceFiles(); + this.oldProgramCachedFiles = { + getSourceFile: (fileName: string) => this.oldProgramEmittedSourceFiles !.get(fileName), + getGeneratedFile: (srcFileName: string, genFileName: string) => + this.oldProgramEmittedGeneratedFiles !.get(genFileName), + }; + } else { + this.oldProgramCachedFiles = oldProgram as CachedFiles; + } } if (options.flatModuleOutFile) { @@ -110,7 +141,9 @@ class AngularCompilerProgram implements Program { (genFile, fileName) => result.set(fileName, genFile)); } if (this.emittedGeneratedFiles) { - this.emittedGeneratedFiles.forEach((genFile) => result.set(genFile.genFileUrl, genFile)); + for (const genFile of this.emittedGeneratedFiles) { + result.set(genFile.genFileName, genFile); + } } return result; } @@ -121,7 +154,9 @@ class AngularCompilerProgram implements Program { this.oldProgramEmittedSourceFiles.forEach((sf, fileName) => result.set(fileName, sf)); } if (this.emittedSourceFiles) { - this.emittedSourceFiles.forEach((sf) => result.set(sf.fileName, sf)); + for (const sf of this.emittedSourceFiles) { + result.set(sf.fileName, sf); + } } return result; } @@ -151,22 +186,27 @@ class AngularCompilerProgram implements Program { return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken); } let diags: ts.Diagnostic[] = []; - this.tsProgram.getSourceFiles().forEach(sf => { + for (const sf of this.tsProgram.getSourceFiles()) { if (!GENERATED_FILES.test(sf.fileName)) { diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken)); } - }); + } return diags; } - getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken): + getNgSemanticDiagnostics(genFile?: GeneratedFile, cancellationToken?: ts.CancellationToken): Diagnostic[] { let diags: ts.Diagnostic[] = []; - this.tsProgram.getSourceFiles().forEach(sf => { - if (GENERATED_FILES.test(sf.fileName) && !sf.isDeclarationFile) { - diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken)); + if (genFile) { + diags.push(...this.tsProgram.getSemanticDiagnostics( + this.tsProgram.getSourceFile(genFile.genFileName), cancellationToken)); + } else { + for (const sf of this.tsProgram.getSourceFiles()) { + if (GENERATED_FILES.test(sf.fileName) && !sf.isDeclarationFile) { + diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken)); + } } - }); + } const {ng} = translateDiagnostics(this.typeCheckHost, diags); return ng; } @@ -187,13 +227,42 @@ class AngularCompilerProgram implements Program { }); } + getGeneratedFile(genFileName: string): GeneratedFile|undefined { + return this.generatedFilesMap.get(genFileName); + } + + getGeneratedFiles(): GeneratedFile[] { return this.generatedFiles; } + + hasChanged(fileName: string) { + if (!this.oldProgramCachedFiles) { + return true; + } + let changed = this._changedFileNames.get(fileName); + if (typeof changed === 'boolean') { + return changed; + } + const genFile = this.getGeneratedFile(fileName); + if (genFile) { + const oldGenFile = this.oldProgramCachedFiles !.getGeneratedFile( + genFile.srcFileName, genFile.genFileName) as GeneratedFile; + changed = !oldGenFile || !genFile.isEquivalent(oldGenFile); + } else { + const sf = this.tsProgram.getSourceFile(fileName); + const oldSf = this.oldProgramCachedFiles !.getSourceFile(fileName); + changed = !oldSf || sf !== oldSf; + } + this._changedFileNames.set(fileName, changed); + return changed; + } + emit( - {emitFlags = EmitFlags.Default, cancellationToken, customTransformers, + {targetFileNames, emitFlags = EmitFlags.Default, cancellationToken, customTransformers, emitCallback = defaultEmitCallback}: { + targetFileNames?: string[], emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken, customTransformers?: CustomTransformers, - emitCallback?: TsEmitCallback + emitCallback?: EmitCallback } = {}): ts.EmitResult { const emitStart = Date.now(); if (emitFlags & EmitFlags.I18nBundle) { @@ -203,88 +272,48 @@ class AngularCompilerProgram implements Program { const bundle = this.compiler.emitMessageBundle(this.analyzedModules, locale); i18nExtract(format, file, this.host, this.options, bundle); } + const emitSkippedResult = {emitSkipped: true, diagnostics: [], emittedFiles: []}; if ((emitFlags & (EmitFlags.JS | EmitFlags.DTS | EmitFlags.Metadata | EmitFlags.Codegen)) === 0) { - return {emitSkipped: true, diagnostics: [], emittedFiles: []}; + return emitSkippedResult; } - let {genFiles, genDiags} = this.generateFilesForEmit(emitFlags); - if (genDiags.length) { - return { - diagnostics: genDiags, - emitSkipped: true, - emittedFiles: [], - }; - } - this.emittedGeneratedFiles = genFiles; + const {srcFilesToEmit, jsonFilesToEmit, emitAllFiles} = this.calcFilesToEmit(targetFileNames); + const outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}> = []; - const genFileByFileName = new Map(); - genFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile)); - this.emittedLibrarySummaries = []; - const emittedSourceFiles = [] as ts.SourceFile[]; 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); - if (!sourceFile.isDeclarationFile && !GENERATED_FILES.test(sourceFile.fileName)) { - // Note: sourceFile is the transformed sourcefile, not the original one! - emittedSourceFiles.push(this.tsProgram.getSourceFile(sourceFile.fileName)); + if (emitFlags & EmitFlags.Codegen) { + genFile = this.getGeneratedFile(sourceFile.fileName); } } - this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles); + this.writeFile(outFileName, outData, writeByteOrderMark, onError, sourceFiles, genFile); }; - const tsCustomTansformers = this.calculateTransforms(genFileByFileName, customTransformers); + const tsCustomTansformers = this.calculateTransforms(customTransformers); const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS; // Restore the original references before we emit so TypeScript doesn't emit // a reference to the .d.ts file. const augmentedReferences = new Map(); - for (const sourceFile of this.tsProgram.getSourceFiles()) { + for (const sourceFile of srcFilesToEmit) { const originalReferences = getOriginalReferences(sourceFile); if (originalReferences) { augmentedReferences.set(sourceFile, sourceFile.referencedFiles); sourceFile.referencedFiles = originalReferences; } } - const genTsFiles: GeneratedFile[] = []; - const genJsonFiles: GeneratedFile[] = []; - genFiles.forEach(gf => { - if (gf.stmts) { - genTsFiles.push(gf); - } - if (gf.source) { - genJsonFiles.push(gf); - } - }); let emitResult: ts.EmitResult; - let emittedUserTsCount: number; try { - const sourceFilesToEmit = this.getSourceFilesForEmit(); - if (sourceFilesToEmit && - (sourceFilesToEmit.length + genTsFiles.length) < MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT) { - const fileNamesToEmit = - [...sourceFilesToEmit.map(sf => sf.fileName), ...genTsFiles.map(gf => gf.genFileUrl)]; - emitResult = mergeEmitResults( - fileNamesToEmit.map((fileName) => emitResult = emitCallback({ - program: this.tsProgram, - host: this.host, - options: this.options, - writeFile: writeTsFile, emitOnlyDtsFiles, - customTransformers: tsCustomTansformers, - targetSourceFile: this.tsProgram.getSourceFile(fileName), - }))); - emittedUserTsCount = sourceFilesToEmit.length; - } else { - emitResult = emitCallback({ - program: this.tsProgram, - host: this.host, - options: this.options, - writeFile: writeTsFile, emitOnlyDtsFiles, - customTransformers: tsCustomTansformers - }); - emittedUserTsCount = this.tsProgram.getSourceFiles().length - genTsFiles.length; - } + emitResult = emitCallback({ + program: this.tsProgram, + host: this.host, + options: this.options, + writeFile: writeTsFile, emitOnlyDtsFiles, + customTransformers: tsCustomTansformers, + targetSourceFiles: emitAllFiles ? undefined : srcFilesToEmit, + }); } finally { // Restore the references back to the augmented value to ensure that the // checks that TypeScript makes for project structure reuse will succeed. @@ -292,7 +321,6 @@ class AngularCompilerProgram implements Program { sourceFile.referencedFiles = references; } } - this.emittedSourceFiles = emittedSourceFiles; if (!outSrcMapping.length) { // if no files were emitted by TypeScript, also don't emit .json files @@ -309,35 +337,99 @@ class AngularCompilerProgram implements Program { const srcToOutPath = createSrcToOutPathMapper(this.options.outDir, sampleSrcFileName, sampleOutFileName); if (emitFlags & EmitFlags.Codegen) { - genJsonFiles.forEach(gf => { - const outFileName = srcToOutPath(gf.genFileUrl); - this.writeFile(outFileName, gf.source !, false, undefined, gf); - }); + this.emitNgSummaryJsonFiles(jsonFilesToEmit, srcToOutPath); } - let metadataJsonCount = 0; if (emitFlags & EmitFlags.Metadata) { - this.tsProgram.getSourceFiles().forEach(sf => { - if (!sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName)) { - metadataJsonCount++; - const metadata = this.metadataCache.getMetadata(sf); - const metadataText = JSON.stringify([metadata]); - const outFileName = srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json')); - this.writeFile(outFileName, metadataText, false, undefined, undefined, [sf]); - } - }); + this.emitMetadataJsonFiles(srcFilesToEmit, srcToOutPath); } const emitEnd = Date.now(); if (this.options.diagnostics) { + const genTsFileCount = srcFilesToEmit.filter(sf => GENERATED_FILES.test(sf.fileName)).length; + let genJsonCount = jsonFilesToEmit.length; + if (emitFlags & EmitFlags.Metadata) { + genJsonCount += genTsFileCount; + } emitResult.diagnostics.push(createMessageDiagnostic([ `Emitted in ${emitEnd - emitStart}ms`, - `- ${emittedUserTsCount} user ts files`, - `- ${genTsFiles.length} generated ts files`, - `- ${genJsonFiles.length + metadataJsonCount} generated json files`, + `- ${srcFilesToEmit.length - genTsFileCount} user ts files`, + `- ${genTsFileCount} generated ts files`, + `- ${genJsonCount} generated json files`, ].join('\n'))); } return emitResult; } + private emitNgSummaryJsonFiles( + jsonFilesToEmit: GeneratedFile[], srcToOutPath: (srcFileName: string) => string) { + for (const gf of jsonFilesToEmit) { + const outFileName = srcToOutPath(gf.genFileName); + this.writeFile(outFileName, gf.source !, false, undefined, undefined, gf); + } + } + + private emitMetadataJsonFiles( + srcFilesToEmit: ts.SourceFile[], srcToOutPath: (srcFileName: string) => string) { + for (const sf of srcFilesToEmit) { + if (!GENERATED_FILES.test(sf.fileName)) { + const metadata = this.metadataCache.getMetadata(sf); + const metadataText = JSON.stringify([metadata]); + const outFileName = srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json')); + this.writeFile(outFileName, metadataText, false); + } + } + } + + private calcFilesToEmit(targetFileNames?: string[]): + {srcFilesToEmit: ts.SourceFile[]; jsonFilesToEmit: GeneratedFile[]; emitAllFiles: boolean;} { + let srcFilesToEmit: ts.SourceFile[]; + let jsonFilesToEmit: GeneratedFile[]; + let emitAllFiles: boolean; + if (targetFileNames) { + emitAllFiles = false; + srcFilesToEmit = []; + jsonFilesToEmit = []; + for (const fileName of targetFileNames) { + const sf = this.tsProgram.getSourceFile(fileName); + if (!sf || sf.isDeclarationFile) continue; + srcFilesToEmit.push(sf); + if (!GENERATED_FILES.test(sf.fileName)) { + // find the .ngsummary.json file and mark it for emit as well + const ngSummaryFileName = sf.fileName.replace(EXT, '') + '.ngsummary.json'; + const ngSummaryGenFile = this.getGeneratedFile(ngSummaryFileName); + if (ngSummaryGenFile) { + jsonFilesToEmit.push(ngSummaryGenFile); + } + } + } + } else { + const changedFiles: ts.SourceFile[] = []; + let useChangedFiles = !!this.oldProgramCachedFiles; + if (useChangedFiles) { + for (const sf of this.tsProgram.getSourceFiles()) { + if (sf.isDeclarationFile) continue; + if (this.hasChanged(sf.fileName)) { + changedFiles.push(sf); + } + if (changedFiles.length > MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT) { + useChangedFiles = false; + break; + } + } + } + if (useChangedFiles) { + emitAllFiles = false; + srcFilesToEmit = changedFiles; + jsonFilesToEmit = this.getGeneratedFiles().filter( + genFile => !!genFile.source && this.hasChanged(genFile.genFileName)); + } else { + emitAllFiles = true; + srcFilesToEmit = this.tsProgram.getSourceFiles().filter(sf => !sf.isDeclarationFile); + jsonFilesToEmit = this.getGeneratedFiles().filter(genFile => !!genFile.source); + } + } + return {srcFilesToEmit, jsonFilesToEmit, emitAllFiles}; + } + // Private members private get compiler(): AotCompiler { if (!this._compiler) { @@ -374,14 +466,26 @@ class AngularCompilerProgram implements Program { return this._typeCheckHost !; } - private calculateTransforms( - genFiles: Map, - customTransformers?: CustomTransformers): ts.CustomTransformers { + private get generatedFilesMap(): Map { + if (!this._generatedFilesMap) { + this.createGenerateFiles(); + } + return this._generatedFilesMap !; + } + + private get generatedFiles(): GeneratedFile[] { + if (!this._generatedFilesMap) { + this.createGenerateFiles(); + } + return this._generatedFiles !; + } + + private calculateTransforms(customTransformers?: CustomTransformers): ts.CustomTransformers { const beforeTs: ts.TransformerFactory[] = []; if (!this.options.disableExpressionLowering) { beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache)); } - beforeTs.push(getAngularEmitterTransformFactory(genFiles)); + beforeTs.push(getAngularEmitterTransformFactory(this)); if (customTransformers && customTransformers.beforeTs) { beforeTs.push(...customTransformers.beforeTs); } @@ -433,20 +537,20 @@ class AngularCompilerProgram implements Program { let rootNames = this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn)); if (this.options.noResolve) { - this.rootNames.forEach(rootName => { + for (const rootName of this.rootNames) { if (hostAdapter.shouldGenerateFilesFor(rootName)) { rootNames.push(...this._compiler.findGeneratedFileNames(rootName)); } - }); + } } const tmpProgram = ts.createProgram(rootNames, this.options, hostAdapter, oldTsProgram); const sourceFiles: string[] = []; - tmpProgram.getSourceFiles().forEach(sf => { + for (const sf of tmpProgram.getSourceFiles()) { if (hostAdapter.isSourceFile(sf.fileName)) { sourceFiles.push(sf.fileName); } - }); + } return {tmpProgram, sourceFiles, hostAdapter, rootNames}; } @@ -455,7 +559,7 @@ class AngularCompilerProgram implements Program { hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) { this._analyzedModules = analyzedModules || emptyModules; if (analyzedModules) { - tmpProgram.getSourceFiles().forEach(sf => { + for (const sf of tmpProgram.getSourceFiles()) { if (sf.fileName.endsWith('.ngfactory.ts')) { const {generate, baseFileName} = hostAdapter.shouldGenerateFile(sf.fileName); if (generate) { @@ -467,7 +571,7 @@ class AngularCompilerProgram implements Program { } } } - }); + } } this._tsProgram = ts.createProgram(rootNames, this.options, hostAdapter, tmpProgram); // Note: the new ts program should be completely reusable by TypeScript as: @@ -504,95 +608,64 @@ class AngularCompilerProgram implements Program { throw e; } - // Note: this returns a ts.Diagnostic so that we - // can return errors in a ts.EmitResult - private generateFilesForEmit(emitFlags: EmitFlags): - {genFiles: GeneratedFile[], genDiags: ts.Diagnostic[]} { - try { - if (!(emitFlags & EmitFlags.Codegen)) { - return {genFiles: [], genDiags: []}; - } - let genFiles = this.compiler.emitAllImpls(this.analyzedModules); - if (this.oldProgramEmittedGeneratedFiles) { - const oldProgramEmittedGeneratedFiles = this.oldProgramEmittedGeneratedFiles; - genFiles = genFiles.filter(genFile => { - const oldGenFile = oldProgramEmittedGeneratedFiles.get(genFile.genFileUrl); - return !oldGenFile || !genFile.isEquivalent(oldGenFile); - }); - } - return {genFiles, genDiags: []}; - } catch (e) { - // TODO(tbosch): check whether we can actually have syntax errors here, - // as we already parsed the metadata and templates before to create the type check block. - if (isSyntaxError(e)) { - const genDiags: ts.Diagnostic[] = [{ - file: undefined, - start: undefined, - length: undefined, - messageText: e.message, - category: ts.DiagnosticCategory.Error, - source: SOURCE, - code: DEFAULT_ERROR_CODE - }]; - return {genFiles: [], genDiags}; - } - throw e; + private createGenerateFiles() { + this._generatedFiles = this.compiler.emitAllImpls(this.analyzedModules); + this._generatedFilesMap = new Map(); + for (const genFile of this._generatedFiles) { + this._generatedFilesMap !.set(genFile.genFileName, genFile); } } - /** - * Returns undefined if all files should be emitted. - */ - private getSourceFilesForEmit(): ts.SourceFile[]|undefined { - // TODO(tbosch): if one of the files contains a `const enum` - // always emit all files -> return undefined! - let sourceFilesToEmit: ts.SourceFile[]|undefined; - if (this.oldProgramEmittedSourceFiles) { - sourceFilesToEmit = this.tsProgram.getSourceFiles().filter(sf => { - const oldFile = this.oldProgramEmittedSourceFiles !.get(sf.fileName); - return !sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName) && sf !== oldFile; - }); - } - return sourceFilesToEmit; - } - private writeFile( outFileName: string, outData: string, writeByteOrderMark: boolean, - onError?: (message: string) => void, genFile?: GeneratedFile, sourceFiles?: ts.SourceFile[]) { + onError?: (message: string) => void, sourceFiles?: ts.SourceFile[], genFile?: GeneratedFile) { + const isGenerated = GENERATED_FILES.test(outFileName); + if (!isGenerated && sourceFiles && sourceFiles.length === 1) { + const sf = sourceFiles[0]; + if (!this.emittedSourceFiles) { + this.emittedSourceFiles = []; + } + // Note: sourceFile is the transformed sourcefile, not the original one! + this.emittedSourceFiles.push(this.tsProgram.getSourceFile(sf.fileName)); + } // collect emittedLibrarySummaries let baseFile: ts.SourceFile|undefined; if (genFile) { - baseFile = this.tsProgram.getSourceFile(genFile.srcFileUrl); + baseFile = this.tsProgram.getSourceFile(genFile.srcFileName); if (baseFile) { if (!this.emittedLibrarySummaries) { this.emittedLibrarySummaries = []; } - if (genFile.genFileUrl.endsWith('.ngsummary.json') && baseFile.fileName.endsWith('.d.ts')) { + if (genFile.genFileName.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}); + this.emittedLibrarySummaries.push({fileName: genFile.genFileName, text: outData}); if (!this.options.declaration) { // If we don't emit declarations, still record an empty .ngfactory.d.ts file, // as we might need it lateron for resolving module names from summaries. - const ngFactoryDts = - genFile.genFileUrl.substring(0, genFile.genFileUrl.length - 15) + '.ngfactory.d.ts'; + const ngFactoryDts = genFile.genFileName.substring(0, genFile.genFileName.length - 15) + + '.ngfactory.d.ts'; this.emittedLibrarySummaries.push({fileName: ngFactoryDts, text: ''}); } } else if (outFileName.endsWith('.d.ts') && baseFile.fileName.endsWith('.d.ts')) { - const dtsSourceFilePath = genFile.genFileUrl.replace(/\.ts$/, '.d.ts'); + const dtsSourceFilePath = genFile.genFileName.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}); } } + if (!this.emittedGeneratedFiles) { + this.emittedGeneratedFiles = []; + } + this.emittedGeneratedFiles.push(genFile); } // Filter out generated files for which we didn't generate code. - // This can happen as the stub caclulation is not completely exact. + // This can happen as the stub calculation 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) { @@ -609,12 +682,13 @@ class AngularCompilerProgram implements Program { } } -export function createProgram( - {rootNames, options, host, oldProgram}: - {rootNames: string[], options: CompilerOptions, host: CompilerHost, oldProgram?: Program}): - Program { +export const createProgram: CreateProgram = ({rootNames, options, host, oldProgram}: { + rootNames: string[], + options: CompilerOptions, + host: CompilerHost, oldProgram?: OldProgram +}) => { return new AngularCompilerProgram(rootNames, options, host, oldProgram); -} +}; // Compute the AotCompiler options function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions { @@ -768,15 +842,3 @@ export function i18nGetExtension(formatName: string): string { throw new Error(`Unsupported format "${formatName}"`); } - -function mergeEmitResults(emitResults: ts.EmitResult[]): ts.EmitResult { - const diagnostics: ts.Diagnostic[] = []; - let emitSkipped = false; - const emittedFiles: string[] = []; - for (const er of emitResults) { - diagnostics.push(...er.diagnostics); - emitSkipped = emitSkipped || er.emitSkipped; - emittedFiles.push(...er.emittedFiles); - } - return {diagnostics, emitSkipped, emittedFiles}; -} diff --git a/packages/compiler-cli/src/transformers/util.ts b/packages/compiler-cli/src/transformers/util.ts index ec774fcde3..ce0d39f317 100644 --- a/packages/compiler-cli/src/transformers/util.ts +++ b/packages/compiler-cli/src/transformers/util.ts @@ -11,6 +11,7 @@ import * as ts from 'typescript'; import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from './api'; export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/; +export const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; export const enum StructureIsReused {Not = 0, SafeModules = 1, Completely = 2} @@ -29,3 +30,7 @@ export function createMessageDiagnostic(messageText: string): ts.Diagnostic&Diag source: SOURCE, }; } + +export function isGeneratedFile(fileName: string): boolean { + return GENERATED_FILES.test(fileName); +} diff --git a/packages/compiler-cli/test/transformers/program_spec.ts b/packages/compiler-cli/test/transformers/program_spec.ts index 100d08fa66..14ecc8592d 100644 --- a/packages/compiler-cli/test/transformers/program_spec.ts +++ b/packages/compiler-cli/test/transformers/program_spec.ts @@ -58,7 +58,8 @@ describe('ng program', () => { function compile( oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions, rootNames?: string[], - host?: CompilerHost): {program: ng.Program, emitResult: ts.EmitResult} { + host?: CompilerHost, + targetFileNames?: string[]): {program: ng.Program, emitResult: ts.EmitResult} { const options = testSupport.createCompilerOptions(overrideOptions); if (!rootNames) { rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')]; @@ -73,7 +74,7 @@ describe('ng program', () => { oldProgram, }); expectNoDiagnosticsInProgram(options, program); - const emitResult = program.emit(); + const emitResult = program.emit({targetFileNames}); return {emitResult, program}; } @@ -475,6 +476,38 @@ describe('ng program', () => { testSupport.shouldNotExist('built/node_modules/lib/index.ngsummary.json'); }); + it('should only emit the given files', () => { + testSupport.writeFiles({ + 'src/main.ts': createModuleAndCompSource('main'), + 'src/main2.ts': createModuleAndCompSource('main2'), + 'src/index.ts': ` + export * from './main'; + export * from './main2'; + ` + }); + compile(undefined, undefined, undefined, undefined, [ + path.resolve(testSupport.basePath, 'src/index.ts'), + path.resolve(testSupport.basePath, 'src/index.ngfactory.ts'), + path.resolve(testSupport.basePath, 'src/main.ts'), + path.resolve(testSupport.basePath, 'src/main.ngfactory.ts') + ]); + testSupport.shouldExist('built/src/main.js'); + testSupport.shouldExist('built/src/main.d.ts'); + testSupport.shouldExist('built/src/main.ngfactory.js'); + testSupport.shouldExist('built/src/main.ngfactory.d.ts'); + testSupport.shouldExist('built/src/main.ngsummary.json'); + testSupport.shouldNotExist('built/src/main2.js'); + testSupport.shouldNotExist('built/src/main2.d.ts'); + testSupport.shouldNotExist('built/src/main2.ngfactory.js'); + testSupport.shouldNotExist('built/src/main2.ngfactory.d.ts'); + testSupport.shouldNotExist('built/src/main2.ngsummary.json'); + testSupport.shouldNotExist('built/node_modules/lib/index.js'); + testSupport.shouldNotExist('built/node_modules/lib/index.d.ts'); + testSupport.shouldNotExist('built/node_modules/lib/index.ngfactory.js'); + testSupport.shouldNotExist('built/node_modules/lib/index.ngfactory.d.ts'); + testSupport.shouldNotExist('built/node_modules/lib/index.ngsummary.json'); + }); + describe('createSrcToOutPathMapper', () => { it('should return identity mapping if no outDir is present', () => { const mapper = createSrcToOutPathMapper(undefined, undefined, undefined); diff --git a/packages/compiler/src/aot/generated_file.ts b/packages/compiler/src/aot/generated_file.ts index 0973fbaa60..a143a586a6 100644 --- a/packages/compiler/src/aot/generated_file.ts +++ b/packages/compiler/src/aot/generated_file.ts @@ -14,7 +14,7 @@ export class GeneratedFile { public stmts: Statement[]|null; constructor( - public srcFileUrl: string, public genFileUrl: string, sourceOrStmts: string|Statement[]) { + public srcFileName: string, public genFileName: string, sourceOrStmts: string|Statement[]) { if (typeof sourceOrStmts === 'string') { this.source = sourceOrStmts; this.stmts = null; @@ -25,7 +25,7 @@ export class GeneratedFile { } isEquivalent(other: GeneratedFile): boolean { - if (this.genFileUrl !== other.genFileUrl) { + if (this.genFileName !== other.genFileName) { return false; } if (this.source) { @@ -42,7 +42,7 @@ export class GeneratedFile { export function toTypeScript(file: GeneratedFile, preamble: string = ''): string { if (!file.stmts) { - throw new Error(`Illegal state: No stmts present on GeneratedFile ${file.genFileUrl}`); + throw new Error(`Illegal state: No stmts present on GeneratedFile ${file.genFileName}`); } - return new TypeScriptEmitter().emitStatements(file.genFileUrl, file.stmts, preamble); + return new TypeScriptEmitter().emitStatements(file.genFileName, file.stmts, preamble); } diff --git a/packages/compiler/test/aot/compiler_spec.ts b/packages/compiler/test/aot/compiler_spec.ts index b9e013b9bd..f23cb615f3 100644 --- a/packages/compiler/test/aot/compiler_spec.ts +++ b/packages/compiler/test/aot/compiler_spec.ts @@ -22,8 +22,8 @@ describe('compiler (unbundled Angular)', () => { describe('Quickstart', () => { it('should compile', () => { const {genFiles} = compile([QUICKSTART, angularFiles]); - expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined(); - expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined(); + expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileName))).toBeDefined(); + expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileName))).toBeDefined(); }); }); @@ -54,7 +54,7 @@ describe('compiler (unbundled Angular)', () => { function compileApp(): GeneratedFile { const {genFiles} = compile([rootDir, angularFiles]); return genFiles.find( - genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts')); + genFile => genFile.srcFileName === componentPath && genFile.genFileName.endsWith('.ts')); } function findLineAndColumn( @@ -127,7 +127,7 @@ describe('compiler (unbundled Angular)', () => { const genFile = compileApp(); const genSource = toTypeScript(genFile); const sourceMap = extractSourceMap(genSource) !; - expect(sourceMap.file).toEqual(genFile.genFileUrl); + expect(sourceMap.file).toEqual(genFile.genFileName); // Note: the generated file also contains code that is not mapped to // the template (e.g. import statements, ...) @@ -322,7 +322,7 @@ describe('compiler (unbundled Angular)', () => { const genFilePreamble = '/* Hello world! */'; const {genFiles} = compile([FILES, angularFiles]); const genFile = - genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')); + genFiles.find(gf => gf.srcFileName === '/app/app.ts' && gf.genFileName.endsWith('.ts')); const genSource = toTypeScript(genFile, genFilePreamble); expect(genSource.startsWith(genFilePreamble)).toBe(true); }); @@ -407,7 +407,7 @@ describe('compiler (unbundled Angular)', () => { }; const result = compile([FILES, angularFiles]); const appModuleFactory = - result.genFiles.find(f => /my-component\.ngfactory/.test(f.genFileUrl)); + result.genFiles.find(f => /my-component\.ngfactory/.test(f.genFileName)); expect(appModuleFactory).toBeDefined(); if (appModuleFactory) { expect(toTypeScript(appModuleFactory)).toContain('MyComponentNgFactory'); @@ -441,7 +441,7 @@ describe('compiler (unbundled Angular)', () => { } }; const {genFiles} = compile([FILES, angularFiles]); - const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts'); + const genFile = genFiles.find(genFile => genFile.srcFileName === '/app/app.ts'); const genSource = toTypeScript(genFile); const createComponentFactoryCall = /ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, ''); // selector @@ -472,7 +472,7 @@ describe('compiler (unbundled Angular)', () => { }; const {genFiles} = compile([FILES, angularFiles]); const genFile = - genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')); + genFiles.find(gf => gf.srcFileName === '/app/app.ts' && gf.genFileName.endsWith('.ts')); const genSource = toTypeScript(genFile); expect(genSource).not.toContain('check('); @@ -510,7 +510,7 @@ describe('compiler (unbundled Angular)', () => { const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true}); const {genFiles: appGenFiles} = compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true}); - const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts'); + const appNgFactory = appGenFiles.find((f) => f.genFileName === '/app/main.ngfactory.ts'); expect(toTypeScript(appNgFactory)).not.toContain('AType'); }); }); @@ -555,7 +555,7 @@ describe('compiler (unbundled Angular)', () => { compile([libInput, getAngularSummaryFiles()], {useSummaries: true}); const {genFiles} = compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true}); - return genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'); + return genFiles.find(gf => gf.srcFileName === '/app/main.ts'); } it('should inherit ctor and lifecycle hooks from classes in other compilation units', () => { @@ -592,7 +592,7 @@ describe('compiler (unbundled Angular)', () => { compile([libInput, getAngularSummaryFiles()], {useSummaries: true}); const {genFiles} = compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true}); - const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'); + const mainNgFactory = genFiles.find(gf => gf.srcFileName === '/app/main.ts'); const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy; expect(toTypeScript(mainNgFactory)) .toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`); @@ -645,7 +645,7 @@ describe('compiler (unbundled Angular)', () => { compile([lib1OutDir, lib2Input, getAngularSummaryFiles()], {useSummaries: true}); const {genFiles} = compile([lib2OutDir, appInput, getAngularSummaryFiles()], {useSummaries: true}); - const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'); + const mainNgFactory = genFiles.find(gf => gf.srcFileName === '/app/main.ts'); const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy; expect(toTypeScript(mainNgFactory)) .toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam_2]`); @@ -803,8 +803,8 @@ describe('compiler (bundled Angular)', () => { describe('Quickstart', () => { it('should compile', () => { const {genFiles} = compile([QUICKSTART, angularFiles]); - expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined(); - expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined(); + expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileName))).toBeDefined(); + expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileName))).toBeDefined(); }); }); diff --git a/packages/compiler/test/aot/jit_summaries_spec.ts b/packages/compiler/test/aot/jit_summaries_spec.ts index dd63c3c411..90ee3211de 100644 --- a/packages/compiler/test/aot/jit_summaries_spec.ts +++ b/packages/compiler/test/aot/jit_summaries_spec.ts @@ -41,7 +41,7 @@ describe('aot summaries for jit', () => { const rootDir = {'app': appDir}; const genFile = - compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); + compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); const genSource = toTypeScript(genFile); expect(genSource).toContain(`import * as i0 from '/app/app.module'`); @@ -70,7 +70,7 @@ describe('aot summaries for jit', () => { const rootDir = {'app': appDir}; const genFile = - compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); + compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); const genSource = toTypeScript(genFile); expect(genSource).toContain(`import * as i0 from '/app/app.module'`); @@ -99,7 +99,7 @@ describe('aot summaries for jit', () => { const rootDir = {'app': appDir}; const genFile = - compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); + compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); const genSource = toTypeScript(genFile); expect(genSource).toContain(`import * as i0 from '/app/app.module'`); @@ -125,7 +125,7 @@ describe('aot summaries for jit', () => { const rootDir = {'app': appDir}; const genFile = - compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); + compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); const genSource = toTypeScript(genFile); expect(genSource).toContain(`import * as i0 from '/app/app.module'`); @@ -164,7 +164,7 @@ describe('aot summaries for jit', () => { const rootDir = {'app': appDir}; const genFile = - compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); + compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); const genSource = toTypeScript(genFile); expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/); @@ -198,7 +198,7 @@ describe('aot summaries for jit', () => { const rootDir = {'app': appDir}; const genFile = - compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); + compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); const genSource = toTypeScript(genFile); expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/); @@ -225,7 +225,7 @@ describe('aot summaries for jit', () => { const rootDir = {'app': appDir}; const genFile = - compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); + compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); const genSource = toTypeScript(genFile); expect(genSource).toMatch( @@ -247,7 +247,7 @@ describe('aot summaries for jit', () => { const rootDir = {'app': appDir}; const genFile = - compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); + compileApp(rootDir).genFiles.find(f => f.genFileName === '/app/app.module.ngsummary.ts'); const genSource = toTypeScript(genFile); expect(genSource).toMatch( @@ -294,9 +294,9 @@ describe('aot summaries for jit', () => { }; const {outDir: lib3In, genFiles: lib2Gen} = compileApp(lib2In, {useSummaries: true}); - const lib2ModuleNgSummary = lib2Gen.find(f => f.genFileUrl === '/lib2/module.ngsummary.ts'); + const lib2ModuleNgSummary = lib2Gen.find(f => f.genFileName === '/lib2/module.ngsummary.ts'); const lib2ReexportNgSummary = - lib2Gen.find(f => f.genFileUrl === '/lib2/reexport.ngsummary.ts'); + lib2Gen.find(f => f.genFileName === '/lib2/reexport.ngsummary.ts'); // ngsummaries should add reexports for imported NgModules from a direct dependency expect(toTypeScript(lib2ModuleNgSummary)) @@ -325,9 +325,9 @@ describe('aot summaries for jit', () => { }; const lib3Gen = compileApp(lib3In, {useSummaries: true}).genFiles; - const lib3ModuleNgSummary = lib3Gen.find(f => f.genFileUrl === '/lib3/module.ngsummary.ts'); + const lib3ModuleNgSummary = lib3Gen.find(f => f.genFileName === '/lib3/module.ngsummary.ts'); const lib3ReexportNgSummary = - lib3Gen.find(f => f.genFileUrl === '/lib3/reexport.ngsummary.ts'); + lib3Gen.find(f => f.genFileName === '/lib3/reexport.ngsummary.ts'); // ngsummary.ts files should use the reexported values from direct and deep deps const lib3ModuleNgSummarySource = toTypeScript(lib3ModuleNgSummary); diff --git a/packages/compiler/test/aot/regression_spec.ts b/packages/compiler/test/aot/regression_spec.ts index 8c768559a1..8cf9c257a2 100644 --- a/packages/compiler/test/aot/regression_spec.ts +++ b/packages/compiler/test/aot/regression_spec.ts @@ -27,6 +27,6 @@ describe('regressions', () => { const {genFiles} = compile( [rootDir, angularFiles], {postCompile: expectNoDiagnostics}, {noUnusedLocals: true, noUnusedParameters: true}); - expect(genFiles.find((f) => f.genFileUrl === '/app/app.module.ngfactory.ts')).toBeTruthy(); + expect(genFiles.find((f) => f.genFileName === '/app/app.module.ngfactory.ts')).toBeTruthy(); }); }); diff --git a/packages/compiler/test/aot/test_util.ts b/packages/compiler/test/aot/test_util.ts index 9910e82a17..99bfa0e899 100644 --- a/packages/compiler/test/aot/test_util.ts +++ b/packages/compiler/test/aot/test_util.ts @@ -658,10 +658,10 @@ export function compile( const genFiles = compiler.emitAllImpls(analyzedModules); genFiles.forEach((file) => { const source = file.source || toTypeScript(file); - if (isSource(file.genFileUrl)) { - host.addScript(file.genFileUrl, source); + if (isSource(file.genFileName)) { + host.addScript(file.genFileName, source); } else { - host.override(file.genFileUrl, source); + host.override(file.genFileName, source); } }); const newProgram = ts.createProgram(host.scriptNames.slice(0), tsSettings, host); @@ -671,7 +671,7 @@ export function compile( } let outDir: MockDirectory = {}; if (emit) { - const dtsFilesWithGenFiles = new Set(genFiles.map(gf => gf.srcFileUrl).filter(isDts)); + const dtsFilesWithGenFiles = new Set(genFiles.map(gf => gf.srcFileName).filter(isDts)); outDir = arrayToMockDir(toMockFileArray([host.writtenFiles, host.overrides]) .filter((entry) => !isSource(entry.fileName))