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 22, 2017
1 parent 08bb738 commit fcbc27c
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 23 deletions.
32 changes: 28 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 @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -156,16 +166,27 @@ 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._cache) {
this._setFileContent(fileName, result);
return result;
} else {
return result;
}
}
return this._files[fileName].content;
}

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

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

return this._files[fileName].getSourceFile(languageVersion, this._setParentNodes);
Expand Down
35 changes: 23 additions & 12 deletions packages/@ngtools/webpack/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
});

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 +261,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 +295,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
22 changes: 17 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 @@ -72,11 +73,22 @@ 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
1 change: 1 addition & 0 deletions tests/e2e/tests/build/scripts-array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default function () {
.then(() => expectFileToMatch('dist/index.html', oneLineTrim`
<script type="text/javascript" src="inline.bundle.js"></script>
<script type="text/javascript" src="polyfills.bundle.js"></script>
<script type="text/javascript" src="angular.bundle.js"></script>
<script type="text/javascript" src="scripts.bundle.js"></script>
<script type="text/javascript" src="renamed-script.bundle.js"></script>
<script type="text/javascript" src="common-entry.bundle.js"></script>
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/tests/build/styles/styles-array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export default function () {
.then(() => expectFileToMatch('dist/index.html', oneLineTrim`
<script type="text/javascript" src="inline.bundle.js"></script>
<script type="text/javascript" src="polyfills.bundle.js"></script>
<script type="text/javascript" src="angular.bundle.js"></script>
<script type="text/javascript" src="common-entry.bundle.js"></script>
<script type="text/javascript" src="vendor.bundle.js"></script>
<script type="text/javascript" src="main.bundle.js"></script>
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/tests/third-party/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default function() {
.then(() => expectFileToMatch('dist/index.html', oneLineTrim`
<script type="text/javascript" src="inline.bundle.js"></script>
<script type="text/javascript" src="polyfills.bundle.js"></script>
<script type="text/javascript" src="angular.bundle.js"></script>
<script type="text/javascript" src="scripts.bundle.js"></script>
<script type="text/javascript" src="vendor.bundle.js"></script>
<script type="text/javascript" src="main.bundle.js"></script>
Expand Down

0 comments on commit fcbc27c

Please sign in to comment.