Skip to content

Commit e9fab5b

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 a2ea05e commit e9fab5b

File tree

4 files changed

+58
-23
lines changed

4 files changed

+58
-23
lines changed

packages/@ngtools/webpack/src/compiler_host.ts

+17-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) {
@@ -156,16 +160,25 @@ export class WebpackCompilerHost implements ts.CompilerHost {
156160
this._changed = false;
157161
}
158162

163+
invalidate(fileName: string): void {
164+
delete this._files[fileName];
165+
}
166+
159167
fileExists(fileName: string): boolean {
160168
fileName = this._resolve(fileName);
161169
return fileName in this._files || this._delegate.fileExists(fileName);
162170
}
163171

164172
readFile(fileName: string): string {
165173
fileName = this._resolve(fileName);
166-
return (fileName in this._files)
167-
? this._files[fileName].content
168-
: this._delegate.readFile(fileName);
174+
if (!(fileName in this._files)) {
175+
const result = this._delegate.readFile(fileName);
176+
if (result) {
177+
this._setFileContent(fileName, result);
178+
return result;
179+
}
180+
}
181+
return this._files[fileName].content;
169182
}
170183

171184
directoryExists(directoryName: string): boolean {
@@ -199,7 +212,7 @@ export class WebpackCompilerHost implements ts.CompilerHost {
199212
fileName = this._resolve(fileName);
200213

201214
if (!(fileName in this._files)) {
202-
return this._delegate.getSourceFile(fileName, languageVersion, onError);
215+
this.readFile(fileName);
203216
}
204217

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

packages/@ngtools/webpack/src/plugin.ts

+19-12
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ export class AotPlugin implements Tapable {
194194
apply(compiler: any) {
195195
this._compiler = compiler;
196196

197+
compiler.plugin('invalid', (fileName: string, timestamp: number) => {
198+
this._compilerHost.invalidate(fileName);
199+
});
200+
197201
compiler.plugin('context-module-factory', (cmf: any) => {
198202
cmf.plugin('before-resolve', (request: any, callback: (err?: any, request?: any) => void) => {
199203
if (!request) {
@@ -253,6 +257,7 @@ export class AotPlugin implements Tapable {
253257
if (this._compilation._ngToolsWebpackPluginInstance) {
254258
return cb(new Error('An @ngtools/webpack plugin already exist for this compilation.'));
255259
}
260+
256261
this._compilation._ngToolsWebpackPluginInstance = this;
257262

258263
this._resourceLoader = new WebpackResourceLoader(compilation);
@@ -286,18 +291,20 @@ export class AotPlugin implements Tapable {
286291
this._rootFilePath, this._compilerOptions, this._compilerHost, this._program);
287292
})
288293
.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);
294+
if (this._typeCheck) {
295+
const diagnostics = this._program.getGlobalDiagnostics();
296+
if (diagnostics.length > 0) {
297+
const message = diagnostics
298+
.map(diagnostic => {
299+
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(
300+
diagnostic.start);
301+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
302+
return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message})`;
303+
})
304+
.join('\n');
305+
306+
throw new Error(message);
307+
}
301308
}
302309
})
303310
.then(() => {

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

packages/angular-cli/models/webpack-build-common.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export function getWebpackCommonConfig(
3737

3838
const appRoot = path.resolve(projectRoot, appConfig.root);
3939
const nodeModules = path.resolve(projectRoot, 'node_modules');
40+
const angularCoreNodeModules = path.resolve(projectRoot, 'node_modules/@angular/core');
4041

4142
let extraPlugins: any[] = [];
4243
let extraRules: any[] = [];
@@ -68,11 +69,22 @@ export function getWebpackCommonConfig(
6869
}
6970

7071
if (vendorChunk) {
71-
extraPlugins.push(new webpack.optimize.CommonsChunkPlugin({
72-
name: 'vendor',
73-
chunks: ['main'],
74-
minChunks: (module: any) => module.userRequest && module.userRequest.startsWith(nodeModules)
75-
}));
72+
extraPlugins.push(
73+
new webpack.optimize.CommonsChunkPlugin({
74+
name: 'vendor',
75+
chunks: ['main'],
76+
minChunks: (module: any) => {
77+
return module.userRequest && module.userRequest.startsWith(nodeModules)
78+
&& !module.userRequest.startsWith(angularCoreNodeModules);
79+
}
80+
}),
81+
new webpack.optimize.CommonsChunkPlugin({
82+
name: 'angular',
83+
chunks: ['main'],
84+
minChunks: (module: any) => {
85+
return module.userRequest && module.userRequest.startsWith(angularCoreNodeModules);
86+
}
87+
}));
7688
}
7789

7890
// process environment file replacement

0 commit comments

Comments
 (0)