Skip to content

Commit

Permalink
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: angular#1980, angular#4020, angular#3315
  • Loading branch information
hansl committed Jan 20, 2017
1 parent a2ea05e commit 2025c67
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 24 deletions.
2 changes: 2 additions & 0 deletions lib/bootstrap-local.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable no-console */
'use strict';

require('source-map-support/register');

const fs = require('fs');
const path = require('path');
const ts = require('typescript');
Expand Down
21 changes: 17 additions & 4 deletions packages/@ngtools/webpack/src/compiler_host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -156,16 +160,25 @@ 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);
}

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 {
Expand Down Expand Up @@ -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);
Expand Down
31 changes: 19 additions & 12 deletions packages/@ngtools/webpack/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(() => {
Expand Down
7 changes: 5 additions & 2 deletions packages/@ngtools/webpack/src/refactor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
21 changes: 16 additions & 5 deletions packages/angular-cli/models/webpack-build-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"outDir": "./dist",
"rootDir": ".",
"sourceMap": true,
"sourceRoot": "",
"sourceRoot": "/",
"inlineSourceMap": true,
"target": "es5",
"lib": ["es6"],
Expand Down

0 comments on commit 2025c67

Please sign in to comment.