Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf(@ngtools/webpack): Improve rebuild performance
Browse files Browse the repository at this point in the history
Keep the TypeScript SourceFile around so we don't regenerate all of them
when we rebuild the Program. The rebuild time is now 40-50% faster for
hello world. This means all the files which haven't changed are not
reparsed.

Mentions: #1980, #4020, #3315
hansl committed Jan 22, 2017
1 parent 4b9af62 commit b293dfe
Showing 3 changed files with 59 additions and 21 deletions.
38 changes: 31 additions & 7 deletions packages/@ngtools/webpack/src/compiler_host.ts
Original file line number Diff line number Diff line change
@@ -68,6 +68,10 @@ export class VirtualFileStats extends VirtualStats {
set content(v: string) {
this._content = v;
this._mtime = new Date();
this._sourceFile = null;
}
setSourceFile(sourceFile: ts.SourceFile) {
this._sourceFile = sourceFile;
}
getSourceFile(languageVersion: ts.ScriptTarget, setParentNodes: boolean) {
if (!this._sourceFile) {
@@ -96,6 +100,8 @@ export class WebpackCompilerHost implements ts.CompilerHost {
private _basePath: string;
private _setParentNodes: boolean;

private _cache: boolean = false;

constructor(private _options: ts.CompilerOptions, basePath: string) {
this._setParentNodes = true;
this._delegate = ts.createCompilerHost(this._options, this._setParentNodes);
@@ -129,6 +135,10 @@ export class WebpackCompilerHost implements ts.CompilerHost {
this._changed = true;
}

enableCaching() {
this._cache = true;
}

populateWebpackResolver(resolver: any) {
const fs = resolver.fileSystem;
if (!this._changed) {
@@ -156,21 +166,32 @@ export class WebpackCompilerHost implements ts.CompilerHost {
this._changed = false;
}

invalidate(fileName: string): void {
delete this._files[fileName];
}

fileExists(fileName: string): boolean {
fileName = this._resolve(fileName);
return fileName in this._files || this._delegate.fileExists(fileName);
return this._files[fileName] != null || this._delegate.fileExists(fileName);
}

readFile(fileName: string): string {
fileName = this._resolve(fileName);
return (fileName in this._files)
? this._files[fileName].content
: this._delegate.readFile(fileName);
if (this._files[fileName] == null) {
const result = this._delegate.readFile(fileName);
if (result !== undefined && this._cache) {
this._setFileContent(fileName, result);
return result;
} else {
return result;
}
}
return this._files[fileName].content;
}

directoryExists(directoryName: string): boolean {
directoryName = this._resolve(directoryName);
return (directoryName in this._directories) || this._delegate.directoryExists(directoryName);
return (this._directories[directoryName] != null) || this._delegate.directoryExists(directoryName);
}

getFiles(path: string): string[] {
@@ -198,8 +219,11 @@ export class WebpackCompilerHost implements ts.CompilerHost {
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: OnErrorFn) {
fileName = this._resolve(fileName);

if (!(fileName in this._files)) {
return this._delegate.getSourceFile(fileName, languageVersion, onError);
if (this._files[fileName] == null) {
const content = this.readFile(fileName);
if (!this._cache) {
return ts.createSourceFile(fileName, content, languageVersion, this._setParentNodes);
}
}

return this._files[fileName].getSourceFile(languageVersion, this._setParentNodes);
35 changes: 23 additions & 12 deletions packages/@ngtools/webpack/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -165,6 +165,10 @@ export class AotPlugin implements Tapable {
this._program = ts.createProgram(
this._rootFilePath, this._compilerOptions, this._compilerHost);

// We enable caching of the filesystem in compilerHost _after_ the program has been created,
// because we don't want SourceFile instances to be cached past this point.
this._compilerHost.enableCaching();

if (options.entryModule) {
this._entryModule = options.entryModule;
} else if ((tsConfig.raw['angularCompilerOptions'] as any)
@@ -194,6 +198,10 @@ export class AotPlugin implements Tapable {
apply(compiler: any) {
this._compiler = compiler;

compiler.plugin('invalid', (fileName: string, timestamp: number) => {
this._compilerHost.invalidate(fileName);
});

// Add lazy modules to the context module for @angular/core/src/linker
compiler.plugin('context-module-factory', (cmf: any) => {
cmf.plugin('after-resolve', (result: any, callback: (err?: any, request?: any) => void) => {
@@ -251,6 +259,7 @@ export class AotPlugin implements Tapable {
if (this._compilation._ngToolsWebpackPluginInstance) {
return cb(new Error('An @ngtools/webpack plugin already exist for this compilation.'));
}

this._compilation._ngToolsWebpackPluginInstance = this;

this._resourceLoader = new WebpackResourceLoader(compilation);
@@ -284,18 +293,20 @@ export class AotPlugin implements Tapable {
this._rootFilePath, this._compilerOptions, this._compilerHost, this._program);
})
.then(() => {
const diagnostics = this._program.getGlobalDiagnostics();
if (diagnostics.length > 0) {
const message = diagnostics
.map(diagnostic => {
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(
diagnostic.start);
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message})`;
})
.join('\n');

throw new Error(message);
if (this._typeCheck) {
const diagnostics = this._program.getGlobalDiagnostics();
if (diagnostics.length > 0) {
const message = diagnostics
.map(diagnostic => {
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(
diagnostic.start);
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message})`;
})
.join('\n');

throw new Error(message);
}
}
})
.then(() => {
7 changes: 5 additions & 2 deletions packages/@ngtools/webpack/src/refactor.ts
Original file line number Diff line number Diff line change
@@ -60,12 +60,15 @@ export class TypeScriptFileRefactor {
if (!this._program) {
return [];
}
let diagnostics: ts.Diagnostic[] = this._program.getSyntacticDiagnostics(this._sourceFile)
.concat(this._program.getSemanticDiagnostics(this._sourceFile));
let diagnostics: ts.Diagnostic[] = [];
// only concat the declaration diagnostics if the tsconfig config sets it to true.
if (this._program.getCompilerOptions().declaration == true) {
diagnostics = diagnostics.concat(this._program.getDeclarationDiagnostics(this._sourceFile));
}
diagnostics = diagnostics.concat(
this._program.getSyntacticDiagnostics(this._sourceFile),
this._program.getSemanticDiagnostics(this._sourceFile));

return diagnostics;
}

0 comments on commit b293dfe

Please sign in to comment.