fix(compiler): only don’t emit already emitted files in incremental compilation

This commit is contained in:
Tobias Bosch 2017-10-03 09:53:58 -07:00 committed by Alex Rickabaugh
parent f3f4c3d835
commit caa51950e8
3 changed files with 59 additions and 19 deletions

View File

@ -318,4 +318,9 @@ export interface Program {
* @internal * @internal
*/ */
getEmittedGeneratedFiles(): Map<string, GeneratedFile>; getEmittedGeneratedFiles(): Map<string, GeneratedFile>;
/**
* @internal
*/
getEmittedSourceFiles(): Map<string, ts.SourceFile>;
} }

View File

@ -42,16 +42,17 @@ class AngularCompilerProgram implements Program {
private metadataCache: LowerMetadataCache; private metadataCache: LowerMetadataCache;
private oldProgramLibrarySummaries: Map<string, LibrarySummary>|undefined; private oldProgramLibrarySummaries: Map<string, LibrarySummary>|undefined;
private oldProgramEmittedGeneratedFiles: Map<string, GeneratedFile>|undefined; private oldProgramEmittedGeneratedFiles: Map<string, GeneratedFile>|undefined;
private oldProgramEmittedSourceFiles: Map<string, ts.SourceFile>|undefined;
// Note: This will be cleared out as soon as we create the _tsProgram // Note: This will be cleared out as soon as we create the _tsProgram
private oldTsProgram: ts.Program|undefined; private oldTsProgram: ts.Program|undefined;
private emittedLibrarySummaries: LibrarySummary[]|undefined; private emittedLibrarySummaries: LibrarySummary[]|undefined;
private emittedGeneratedFiles: GeneratedFile[]|undefined; private emittedGeneratedFiles: GeneratedFile[]|undefined;
private emittedSourceFiles: ts.SourceFile[]|undefined;
// Lazily initialized fields // Lazily initialized fields
private _typeCheckHost: TypeCheckHost; private _typeCheckHost: TypeCheckHost;
private _compiler: AotCompiler; private _compiler: AotCompiler;
private _tsProgram: ts.Program; private _tsProgram: ts.Program;
private _changedNonGenFileNames: string[]|undefined;
private _analyzedModules: NgAnalyzedModules|undefined; private _analyzedModules: NgAnalyzedModules|undefined;
private _structuralDiagnostics: Diagnostic[]|undefined; private _structuralDiagnostics: Diagnostic[]|undefined;
private _programWithStubs: ts.Program|undefined; private _programWithStubs: ts.Program|undefined;
@ -69,6 +70,7 @@ class AngularCompilerProgram implements Program {
if (oldProgram) { if (oldProgram) {
this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries(); this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries();
this.oldProgramEmittedGeneratedFiles = oldProgram.getEmittedGeneratedFiles(); this.oldProgramEmittedGeneratedFiles = oldProgram.getEmittedGeneratedFiles();
this.oldProgramEmittedSourceFiles = oldProgram.getEmittedSourceFiles();
} }
if (options.flatModuleOutFile) { if (options.flatModuleOutFile) {
@ -114,6 +116,17 @@ class AngularCompilerProgram implements Program {
return result; return result;
} }
getEmittedSourceFiles(): Map<string, ts.SourceFile> {
const result = new Map<string, ts.SourceFile>();
if (this.oldProgramEmittedSourceFiles) {
this.oldProgramEmittedSourceFiles.forEach((sf, fileName) => result.set(fileName, sf));
}
if (this.emittedSourceFiles) {
this.emittedSourceFiles.forEach((sf) => result.set(sf.fileName, sf));
}
return result;
}
getTsProgram(): ts.Program { return this.tsProgram; } getTsProgram(): ts.Program { return this.tsProgram; }
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken) { getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken) {
@ -192,6 +205,7 @@ class AngularCompilerProgram implements Program {
const genFileByFileName = new Map<string, GeneratedFile>(); const genFileByFileName = new Map<string, GeneratedFile>();
genFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile)); genFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile));
this.emittedLibrarySummaries = []; this.emittedLibrarySummaries = [];
const emittedSourceFiles = [] as ts.SourceFile[];
const writeTsFile: ts.WriteFileCallback = const writeTsFile: ts.WriteFileCallback =
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => { (outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null; const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
@ -199,6 +213,10 @@ class AngularCompilerProgram implements Program {
if (sourceFile) { if (sourceFile) {
outSrcMapping.push({outFileName: outFileName, sourceFile}); outSrcMapping.push({outFileName: outFileName, sourceFile});
genFile = genFileByFileName.get(sourceFile.fileName); 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));
}
} }
this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles); this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
}; };
@ -227,12 +245,11 @@ class AngularCompilerProgram implements Program {
let emitResult: ts.EmitResult; let emitResult: ts.EmitResult;
let emittedUserTsCount: number; let emittedUserTsCount: number;
try { try {
const useSingleFileEmit = this._changedNonGenFileNames && const sourceFilesToEmit = this.getSourceFilesForEmit();
(this._changedNonGenFileNames.length + genTsFiles.length) < if (sourceFilesToEmit &&
MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT; (sourceFilesToEmit.length + genTsFiles.length) < MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT) {
if (useSingleFileEmit) {
const fileNamesToEmit = const fileNamesToEmit =
[...this._changedNonGenFileNames !, ...genTsFiles.map(gf => gf.genFileUrl)]; [...sourceFilesToEmit.map(sf => sf.fileName), ...genTsFiles.map(gf => gf.genFileUrl)];
emitResult = mergeEmitResults( emitResult = mergeEmitResults(
fileNamesToEmit.map((fileName) => emitResult = emitCallback({ fileNamesToEmit.map((fileName) => emitResult = emitCallback({
program: this.tsProgram, program: this.tsProgram,
@ -242,7 +259,7 @@ class AngularCompilerProgram implements Program {
customTransformers: tsCustomTansformers, customTransformers: tsCustomTansformers,
targetSourceFile: this.tsProgram.getSourceFile(fileName), targetSourceFile: this.tsProgram.getSourceFile(fileName),
}))); })));
emittedUserTsCount = this._changedNonGenFileNames !.length; emittedUserTsCount = sourceFilesToEmit.length;
} else { } else {
emitResult = emitCallback({ emitResult = emitCallback({
program: this.tsProgram, program: this.tsProgram,
@ -260,6 +277,7 @@ class AngularCompilerProgram implements Program {
sourceFile.referencedFiles = references; sourceFile.referencedFiles = references;
} }
} }
this.emittedSourceFiles = emittedSourceFiles;
if (!outSrcMapping.length) { if (!outSrcMapping.length) {
// if no files were emitted by TypeScript, also don't emit .json files // if no files were emitted by TypeScript, also don't emit .json files
@ -419,16 +437,6 @@ class AngularCompilerProgram implements Program {
sourceFiles.push(sf.fileName); sourceFiles.push(sf.fileName);
} }
}); });
if (oldTsProgram) {
// TODO(tbosch): if one of the files contains a `const enum`
// always emit all files!
const changedNonGenFileNames = this._changedNonGenFileNames = [] as string[];
tmpProgram.getSourceFiles().forEach(sf => {
if (!GENERATED_FILES.test(sf.fileName) && oldTsProgram.getSourceFile(sf.fileName) !== sf) {
changedNonGenFileNames.push(sf.fileName);
}
});
}
return {tmpProgram, sourceFiles, hostAdapter, rootNames}; return {tmpProgram, sourceFiles, hostAdapter, rootNames};
} }
@ -522,6 +530,22 @@ class AngularCompilerProgram implements Program {
} }
} }
/**
* 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 generateSemanticDiagnostics(): {ts: ts.Diagnostic[], ng: Diagnostic[]} { private generateSemanticDiagnostics(): {ts: ts.Diagnostic[], ng: Diagnostic[]} {
return translateDiagnostics(this.typeCheckHost, this.tsProgram.getSemanticDiagnostics()); return translateDiagnostics(this.typeCheckHost, this.tsProgram.getSemanticDiagnostics());
} }

View File

@ -180,7 +180,7 @@ describe('ng program', () => {
// compile libraries // compile libraries
const p1 = compile(undefined, options, undefined, host).program; const p1 = compile(undefined, options, undefined, host).program;
// first compile without libraries // compile without libraries
const p2 = compile(p1, options, undefined, host).program; const p2 = compile(p1, options, undefined, host).program;
expect(written.has(path.resolve(testSupport.basePath, 'built/src/index.js'))).toBe(true); expect(written.has(path.resolve(testSupport.basePath, 'built/src/index.js'))).toBe(true);
let ngFactoryContent = let ngFactoryContent =
@ -202,11 +202,22 @@ describe('ng program', () => {
// change a file that is input to generated files // change a file that is input to generated files
written.clear(); written.clear();
testSupport.writeFiles({'src/index.html': 'Hello'}); testSupport.writeFiles({'src/index.html': 'Hello'});
compile(p4, options, undefined, host); const p5 = compile(p4, options, undefined, host).program;
expect(written.size).toBe(1); expect(written.size).toBe(1);
ngFactoryContent = ngFactoryContent =
written.get(path.resolve(testSupport.basePath, 'built/src/index.ngfactory.js')); written.get(path.resolve(testSupport.basePath, 'built/src/index.ngfactory.js'));
expect(ngFactoryContent).toMatch(/Hello/); expect(ngFactoryContent).toMatch(/Hello/);
// change a file and create an intermediate program that is not emitted
written.clear();
fileCache.delete(path.resolve(testSupport.basePath, 'src/index.ts'));
const p6 = ng.createProgram({
rootNames: [path.resolve(testSupport.basePath, 'src/index.ts')],
options: testSupport.createCompilerOptions(options), host,
oldProgram: p5
});
const p7 = compile(p6, options, undefined, host).program;
expect(written.size).toBe(1);
}); });
it('should set emitSkipped to false for full and incremental emit', () => { it('should set emitSkipped to false for full and incremental emit', () => {