Skip to content

Commit 7ea20bc

Browse files
committed
perf(@ngtools/webpack): Improve rebuild performance
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: angular#1980, angular#4020, angular#3315
1 parent 08bb738 commit 7ea20bc

File tree

3 files changed

+56
-18
lines changed

3 files changed

+56
-18
lines changed

Diff for: packages/@ngtools/webpack/src/compiler_host.ts

+28-4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ export class VirtualFileStats extends VirtualStats {
6868
set content(v: string) {
6969
this._content = v;
7070
this._mtime = new Date();
71+
this._sourceFile = null;
72+
}
73+
setSourceFile(sourceFile: ts.SourceFile) {
74+
this._sourceFile = sourceFile;
7175
}
7276
getSourceFile(languageVersion: ts.ScriptTarget, setParentNodes: boolean) {
7377
if (!this._sourceFile) {
@@ -96,6 +100,8 @@ export class WebpackCompilerHost implements ts.CompilerHost {
96100
private _basePath: string;
97101
private _setParentNodes: boolean;
98102

103+
private _cache: boolean = false;
104+
99105
constructor(private _options: ts.CompilerOptions, basePath: string) {
100106
this._setParentNodes = true;
101107
this._delegate = ts.createCompilerHost(this._options, this._setParentNodes);
@@ -129,6 +135,10 @@ export class WebpackCompilerHost implements ts.CompilerHost {
129135
this._changed = true;
130136
}
131137

138+
enableCaching() {
139+
this._cache = true;
140+
}
141+
132142
populateWebpackResolver(resolver: any) {
133143
const fs = resolver.fileSystem;
134144
if (!this._changed) {
@@ -156,16 +166,27 @@ export class WebpackCompilerHost implements ts.CompilerHost {
156166
this._changed = false;
157167
}
158168

169+
invalidate(fileName: string): void {
170+
delete this._files[fileName];
171+
}
172+
159173
fileExists(fileName: string): boolean {
160174
fileName = this._resolve(fileName);
161175
return fileName in this._files || this._delegate.fileExists(fileName);
162176
}
163177

164178
readFile(fileName: string): string {
165179
fileName = this._resolve(fileName);
166-
return (fileName in this._files)
167-
? this._files[fileName].content
168-
: this._delegate.readFile(fileName);
180+
if (!(fileName in this._files)) {
181+
const result = this._delegate.readFile(fileName);
182+
if (result && this._cache) {
183+
this._setFileContent(fileName, result);
184+
return result;
185+
} else {
186+
return result;
187+
}
188+
}
189+
return this._files[fileName].content;
169190
}
170191

171192
directoryExists(directoryName: string): boolean {
@@ -199,7 +220,10 @@ export class WebpackCompilerHost implements ts.CompilerHost {
199220
fileName = this._resolve(fileName);
200221

201222
if (!(fileName in this._files)) {
202-
return this._delegate.getSourceFile(fileName, languageVersion, onError);
223+
const content = this.readFile(fileName);
224+
if (!this._cache) {
225+
return ts.createSourceFile(fileName, content, languageVersion, this._setParentNodes);
226+
}
203227
}
204228

205229
return this._files[fileName].getSourceFile(languageVersion, this._setParentNodes);

Diff for: packages/@ngtools/webpack/src/plugin.ts

+23-12
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ export class AotPlugin implements Tapable {
165165
this._program = ts.createProgram(
166166
this._rootFilePath, this._compilerOptions, this._compilerHost);
167167

168+
// We enable caching of the filesystem in compilerHost _after_ the program has been created,
169+
// because we don't want SourceFile instances to be cached past this point.
170+
this._compilerHost.enableCaching();
171+
168172
if (options.entryModule) {
169173
this._entryModule = options.entryModule;
170174
} else if ((tsConfig.raw['angularCompilerOptions'] as any)
@@ -194,6 +198,10 @@ export class AotPlugin implements Tapable {
194198
apply(compiler: any) {
195199
this._compiler = compiler;
196200

201+
compiler.plugin('invalid', (fileName: string, timestamp: number) => {
202+
this._compilerHost.invalidate(fileName);
203+
});
204+
197205
compiler.plugin('context-module-factory', (cmf: any) => {
198206
cmf.plugin('before-resolve', (request: any, callback: (err?: any, request?: any) => void) => {
199207
if (!request) {
@@ -253,6 +261,7 @@ export class AotPlugin implements Tapable {
253261
if (this._compilation._ngToolsWebpackPluginInstance) {
254262
return cb(new Error('An @ngtools/webpack plugin already exist for this compilation.'));
255263
}
264+
256265
this._compilation._ngToolsWebpackPluginInstance = this;
257266

258267
this._resourceLoader = new WebpackResourceLoader(compilation);
@@ -286,18 +295,20 @@ export class AotPlugin implements Tapable {
286295
this._rootFilePath, this._compilerOptions, this._compilerHost, this._program);
287296
})
288297
.then(() => {
289-
const diagnostics = this._program.getGlobalDiagnostics();
290-
if (diagnostics.length > 0) {
291-
const message = diagnostics
292-
.map(diagnostic => {
293-
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(
294-
diagnostic.start);
295-
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
296-
return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message})`;
297-
})
298-
.join('\n');
299-
300-
throw new Error(message);
298+
if (this._typeCheck) {
299+
const diagnostics = this._program.getGlobalDiagnostics();
300+
if (diagnostics.length > 0) {
301+
const message = diagnostics
302+
.map(diagnostic => {
303+
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(
304+
diagnostic.start);
305+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
306+
return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message})`;
307+
})
308+
.join('\n');
309+
310+
throw new Error(message);
311+
}
301312
}
302313
})
303314
.then(() => {

Diff for: packages/@ngtools/webpack/src/refactor.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,15 @@ export class TypeScriptFileRefactor {
6060
if (!this._program) {
6161
return [];
6262
}
63-
let diagnostics: ts.Diagnostic[] = this._program.getSyntacticDiagnostics(this._sourceFile)
64-
.concat(this._program.getSemanticDiagnostics(this._sourceFile));
63+
let diagnostics: ts.Diagnostic[] = [];
6564
// only concat the declaration diagnostics if the tsconfig config sets it to true.
6665
if (this._program.getCompilerOptions().declaration == true) {
6766
diagnostics = diagnostics.concat(this._program.getDeclarationDiagnostics(this._sourceFile));
6867
}
68+
diagnostics = diagnostics.concat(
69+
this._program.getSyntacticDiagnostics(this._sourceFile),
70+
this._program.getSemanticDiagnostics(this._sourceFile));
71+
6972
return diagnostics;
7073
}
7174

0 commit comments

Comments
 (0)