perf(compiler): only emit changed files for incremental compilation
For now, we always create all generated files, but diff them before we pass them to TypeScript. For the user files, we compare the programs and only emit changed TypeScript files. This also adds more diagnostic messages if the `—diagnostics` flag is passed to the command line.
This commit is contained in:

committed by
Alex Rickabaugh

parent
b0868915ae
commit
745b59f49c
@ -86,10 +86,9 @@ describe('perform watch', () => {
|
||||
|
||||
// trigger a single file change
|
||||
// -> all other files should be cached
|
||||
fs.unlinkSync(mainNgFactory);
|
||||
host.triggerFileChange(FileChangeEvent.Change, utilTsPath);
|
||||
expectNoDiagnostics(config.options, host.diagnostics);
|
||||
|
||||
expect(fs.existsSync(mainNgFactory)).toBe(true);
|
||||
expect(fileExistsSpy !).not.toHaveBeenCalledWith(mainTsPath);
|
||||
expect(fileExistsSpy !).toHaveBeenCalledWith(utilTsPath);
|
||||
expect(getSourceFileSpy !).not.toHaveBeenCalledWith(mainTsPath, ts.ScriptTarget.ES5);
|
||||
@ -97,11 +96,10 @@ describe('perform watch', () => {
|
||||
|
||||
// trigger a folder change
|
||||
// -> nothing should be cached
|
||||
fs.unlinkSync(mainNgFactory);
|
||||
host.triggerFileChange(
|
||||
FileChangeEvent.CreateDeleteDir, path.resolve(testSupport.basePath, 'src'));
|
||||
expectNoDiagnostics(config.options, host.diagnostics);
|
||||
|
||||
expect(fs.existsSync(mainNgFactory)).toBe(true);
|
||||
expect(fileExistsSpy !).toHaveBeenCalledWith(mainTsPath);
|
||||
expect(fileExistsSpy !).toHaveBeenCalledWith(utilTsPath);
|
||||
expect(getSourceFileSpy !).toHaveBeenCalledWith(mainTsPath, ts.ScriptTarget.ES5);
|
||||
|
@ -110,8 +110,9 @@ export function setup(): TestSupport {
|
||||
}
|
||||
|
||||
export function expectNoDiagnostics(options: ng.CompilerOptions, diags: ng.Diagnostics) {
|
||||
if (diags.length) {
|
||||
throw new Error(`Expected no diagnostics: ${ng.formatDiagnostics(options, diags)}`);
|
||||
const errorDiags = diags.filter(d => d.category !== ts.DiagnosticCategory.Message);
|
||||
if (errorDiags.length) {
|
||||
throw new Error(`Expected no diagnostics: ${ng.formatDiagnostics(options, errorDiags)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ describe('NgCompilerHost', () => {
|
||||
} = {}) {
|
||||
return new TsCompilerAotCompilerTypeCheckHostAdapter(
|
||||
['/tmp/index.ts'], options, ngHost, new MetadataCollector(), codeGenerator,
|
||||
librarySummaries);
|
||||
new Map(librarySummaries.map(entry => [entry.fileName, entry] as[string, LibrarySummary])));
|
||||
}
|
||||
|
||||
describe('fileNameToModuleName', () => {
|
||||
|
@ -57,17 +57,20 @@ describe('ng program', () => {
|
||||
}
|
||||
|
||||
function compile(
|
||||
oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions,
|
||||
rootNames?: string[]): ng.Program {
|
||||
oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions, rootNames?: string[],
|
||||
host?: CompilerHost): ng.Program {
|
||||
const options = testSupport.createCompilerOptions(overrideOptions);
|
||||
if (!rootNames) {
|
||||
rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')];
|
||||
}
|
||||
|
||||
if (!host) {
|
||||
host = ng.createCompilerHost({options});
|
||||
}
|
||||
const program = ng.createProgram({
|
||||
rootNames: rootNames,
|
||||
options,
|
||||
host: ng.createCompilerHost({options}), oldProgram,
|
||||
host,
|
||||
oldProgram,
|
||||
});
|
||||
expectNoDiagnosticsInProgram(options, program);
|
||||
program.emit();
|
||||
@ -153,6 +156,59 @@ describe('ng program', () => {
|
||||
.toBe(false);
|
||||
});
|
||||
|
||||
it('should only emit changed files', () => {
|
||||
testSupport.writeFiles({
|
||||
'src/index.ts': createModuleAndCompSource('comp', 'index.html'),
|
||||
'src/index.html': `Start`
|
||||
});
|
||||
const options: ng.CompilerOptions = {declaration: false};
|
||||
const host = ng.createCompilerHost({options});
|
||||
const originalGetSourceFile = host.getSourceFile;
|
||||
const fileCache = new Map<string, ts.SourceFile>();
|
||||
host.getSourceFile = (fileName: string) => {
|
||||
if (fileCache.has(fileName)) {
|
||||
return fileCache.get(fileName);
|
||||
}
|
||||
const sf = originalGetSourceFile.call(host, fileName);
|
||||
fileCache.set(fileName, sf);
|
||||
return sf;
|
||||
};
|
||||
|
||||
const written = new Map<string, string>();
|
||||
host.writeFile = (fileName: string, data: string) => written.set(fileName, data);
|
||||
|
||||
// compile libraries
|
||||
const p1 = compile(undefined, options, undefined, host);
|
||||
|
||||
// first compile without libraries
|
||||
const p2 = compile(p1, options, undefined, host);
|
||||
expect(written.has(path.resolve(testSupport.basePath, 'built/src/index.js'))).toBe(true);
|
||||
let ngFactoryContent =
|
||||
written.get(path.resolve(testSupport.basePath, 'built/src/index.ngfactory.js'));
|
||||
expect(ngFactoryContent).toMatch(/Start/);
|
||||
|
||||
// no change -> no emit
|
||||
written.clear();
|
||||
const p3 = compile(p2, options, undefined, host);
|
||||
expect(written.size).toBe(0);
|
||||
|
||||
// change a user file
|
||||
written.clear();
|
||||
fileCache.delete(path.resolve(testSupport.basePath, 'src/index.ts'));
|
||||
const p4 = compile(p3, options, undefined, host);
|
||||
expect(written.size).toBe(1);
|
||||
expect(written.has(path.resolve(testSupport.basePath, 'built/src/index.js'))).toBe(true);
|
||||
|
||||
// change a file that is input to generated files
|
||||
written.clear();
|
||||
testSupport.writeFiles({'src/index.html': 'Hello'});
|
||||
const p5 = compile(p4, options, undefined, host);
|
||||
expect(written.size).toBe(1);
|
||||
ngFactoryContent =
|
||||
written.get(path.resolve(testSupport.basePath, 'built/src/index.ngfactory.js'));
|
||||
expect(ngFactoryContent).toMatch(/Hello/);
|
||||
});
|
||||
|
||||
it('should store library summaries on emit', () => {
|
||||
compileLib('lib');
|
||||
testSupport.writeFiles({
|
||||
@ -163,17 +219,19 @@ describe('ng program', () => {
|
||||
`
|
||||
});
|
||||
const p1 = compile();
|
||||
expect(p1.getLibrarySummaries().some(
|
||||
sf => /node_modules\/lib\/index\.ngfactory\.d\.ts$/.test(sf.fileName)))
|
||||
expect(Array.from(p1.getLibrarySummaries().values())
|
||||
.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)))
|
||||
expect(Array.from(p1.getLibrarySummaries().values())
|
||||
.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)))
|
||||
expect(Array.from(p1.getLibrarySummaries().values())
|
||||
.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);
|
||||
expect(Array.from(p1.getLibrarySummaries().values())
|
||||
.some(sf => /src\/main.*$/.test(sf.fileName)))
|
||||
.toBe(false);
|
||||
});
|
||||
|
||||
it('should reuse the old ts program completely if nothing changed', () => {
|
||||
|
Reference in New Issue
Block a user