From b11a34a96e12d3fa2e6926cf11f05d347fc89b02 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 18 Jan 2017 20:39:51 -0800 Subject: [PATCH] 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: #1980, #4020, #3315 --- .../@ngtools/webpack/src/compiler_host.ts | 21 ++++++++++--- packages/@ngtools/webpack/src/plugin.ts | 31 ++++++++++++------- packages/@ngtools/webpack/src/refactor.ts | 7 +++-- .../models/webpack-build-common.ts | 21 ++++++++++--- 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/packages/@ngtools/webpack/src/compiler_host.ts b/packages/@ngtools/webpack/src/compiler_host.ts index b1ff333a8944..49dce55681f1 100644 --- a/packages/@ngtools/webpack/src/compiler_host.ts +++ b/packages/@ngtools/webpack/src/compiler_host.ts @@ -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) { @@ -156,6 +160,10 @@ 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); @@ -163,9 +171,14 @@ export class WebpackCompilerHost implements ts.CompilerHost { readFile(fileName: string): string { fileName = this._resolve(fileName); - return (fileName in this._files) - ? this._files[fileName].content - : this._delegate.readFile(fileName); + if (!(fileName in this._files)) { + const result = this._delegate.readFile(fileName); + if (result) { + this._setFileContent(fileName, result); + return result; + } + } + return this._files[fileName].content; } directoryExists(directoryName: string): boolean { @@ -199,7 +212,7 @@ export class WebpackCompilerHost implements ts.CompilerHost { fileName = this._resolve(fileName); if (!(fileName in this._files)) { - return this._delegate.getSourceFile(fileName, languageVersion, onError); + this.readFile(fileName); } return this._files[fileName].getSourceFile(languageVersion, this._setParentNodes); diff --git a/packages/@ngtools/webpack/src/plugin.ts b/packages/@ngtools/webpack/src/plugin.ts index 3c43a823b574..202ea8c5fd11 100644 --- a/packages/@ngtools/webpack/src/plugin.ts +++ b/packages/@ngtools/webpack/src/plugin.ts @@ -194,6 +194,10 @@ export class AotPlugin implements Tapable { apply(compiler: any) { this._compiler = compiler; + compiler.plugin('invalid', (fileName: string, timestamp: number) => { + this._compilerHost.invalidate(fileName); + }); + compiler.plugin('context-module-factory', (cmf: any) => { cmf.plugin('before-resolve', (request: any, callback: (err?: any, request?: any) => void) => { if (!request) { @@ -253,6 +257,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); @@ -286,18 +291,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(() => { diff --git a/packages/@ngtools/webpack/src/refactor.ts b/packages/@ngtools/webpack/src/refactor.ts index 105920bb65a2..57a2e6937277 100644 --- a/packages/@ngtools/webpack/src/refactor.ts +++ b/packages/@ngtools/webpack/src/refactor.ts @@ -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; } diff --git a/packages/angular-cli/models/webpack-build-common.ts b/packages/angular-cli/models/webpack-build-common.ts index ae2d05e698cd..910b82585991 100644 --- a/packages/angular-cli/models/webpack-build-common.ts +++ b/packages/angular-cli/models/webpack-build-common.ts @@ -37,6 +37,7 @@ export function getWebpackCommonConfig( const appRoot = path.resolve(projectRoot, appConfig.root); const nodeModules = path.resolve(projectRoot, 'node_modules'); + const angularCoreNodeModules = path.resolve(projectRoot, 'node_modules/@angular/core'); let extraPlugins: any[] = []; let extraRules: any[] = []; @@ -68,11 +69,21 @@ export function getWebpackCommonConfig( } if (vendorChunk) { - extraPlugins.push(new webpack.optimize.CommonsChunkPlugin({ - name: 'vendor', - chunks: ['main'], - minChunks: (module: any) => module.userRequest && module.userRequest.startsWith(nodeModules) - })); + extraPlugins.push( + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + chunks: ['main'], + minChunks: (module: any) => { + return module.userRequest && module.userRequest.startsWith(nodeModules) + && !module.userRequest.startsWith(angularCoreNodeModules); + }), + new webpack.optimize.CommonsChunkPlugin({ + name: 'angular', + chunks: ['main'], + minChunks: (module: any) => { + return module.userRequest && module.userRequest.startsWith(angularCoreNodeModules); + } + })); } // process environment file replacement