Skip to content

Commit

Permalink
perf(@ngtools/webpack): reduce non-watch mode TypeScript diagnostic a…
Browse files Browse the repository at this point in the history
…nalysis overhead

When not in a watch mode, the analyis performed by TypeScript to improve incremental type checking can be avoided by creating an abstract builder program that only wraps the underlying TypeScript program.
Performance enhancements in the upcoming TypeScript 4.3 may remove the need for this.  However, TypeScript 4.3 is not yet released and is not yet supported. In addition, TypeScript 4.2 will continue to be supported throughout the v12 major even when TypeScript 4.3 is also supported.
  • Loading branch information
clydin authored and alan-agius4 committed May 19, 2021
1 parent 1080a52 commit 4f2df00
Showing 1 changed file with 56 additions and 47 deletions.
103 changes: 56 additions & 47 deletions packages/ngtools/webpack/src/ivy/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,10 +377,11 @@ export class AngularWebpackPlugin {
}

private loadConfiguration(compilation: WebpackCompilation) {
const { options: compilerOptions, rootNames, errors } = readConfiguration(
this.pluginOptions.tsconfig,
this.pluginOptions.compilerOptions,
);
const {
options: compilerOptions,
rootNames,
errors,
} = readConfiguration(this.pluginOptions.tsconfig, this.pluginOptions.compilerOptions);
compilerOptions.enableIvy = true;
compilerOptions.noEmitOnError = false;
compilerOptions.suppressOutputPathCheck = true;
Expand Down Expand Up @@ -425,51 +426,57 @@ export class AngularWebpackPlugin {
const typeScriptProgram = angularProgram.getTsProgram();
augmentProgramWithVersioning(typeScriptProgram);

const builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
typeScriptProgram,
host,
this.builder,
);

// Save for next rebuild
let builder: ts.BuilderProgram | ts.EmitAndSemanticDiagnosticsBuilderProgram;
if (this.watchMode) {
this.builder = builder;
builder = this.builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
typeScriptProgram,
host,
this.builder,
);
this.ngtscNextProgram = angularProgram;
} else {
// When not in watch mode, the startup cost of the incremental analysis can be avoided by
// using an abstract builder that only wraps a TypeScript program.
builder = ts.createAbstractBuilder(typeScriptProgram, host);
}

// Update semantic diagnostics cache
const affectedFiles = new Set<ts.SourceFile>();
// eslint-disable-next-line no-constant-condition
while (true) {
const result = builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => {
// If the affected file is a TTC shim, add the shim's original source file.
// This ensures that changes that affect TTC are typechecked even when the changes
// are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes.
// For example, changing @Input property types of a directive used in another component's
// template.
if (
ignoreForDiagnostics.has(sourceFile) &&
sourceFile.fileName.endsWith('.ngtypecheck.ts')
) {
// This file name conversion relies on internal compiler logic and should be converted
// to an official method when available. 15 is length of `.ngtypecheck.ts`
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
const originalSourceFile = builder.getSourceFile(originalFilename);
if (originalSourceFile) {
affectedFiles.add(originalSourceFile);

// Analyze affected files when in watch mode for incremental type checking
if ('getSemanticDiagnosticsOfNextAffectedFile' in builder) {
// eslint-disable-next-line no-constant-condition
while (true) {
const result = builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => {
// If the affected file is a TTC shim, add the shim's original source file.
// This ensures that changes that affect TTC are typechecked even when the changes
// are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes.
// For example, changing @Input property types of a directive used in another component's
// template.
if (
ignoreForDiagnostics.has(sourceFile) &&
sourceFile.fileName.endsWith('.ngtypecheck.ts')
) {
// This file name conversion relies on internal compiler logic and should be converted
// to an official method when available. 15 is length of `.ngtypecheck.ts`
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
const originalSourceFile = builder.getSourceFile(originalFilename);
if (originalSourceFile) {
affectedFiles.add(originalSourceFile);
}

return true;
}

return true;
}
return false;
});

return false;
});
if (!result) {
break;
}

if (!result) {
break;
affectedFiles.add(result.affected as ts.SourceFile);
}

affectedFiles.add(result.affected as ts.SourceFile);
}

// Collect non-semantic diagnostics
Expand Down Expand Up @@ -581,16 +588,18 @@ export class AngularWebpackPlugin {
host: CompilerHost,
diagnosticsReporter: DiagnosticsReporter,
) {
const builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
rootNames,
compilerOptions,
host,
this.builder,
);

// Save for next rebuild
let builder;
if (this.watchMode) {
this.builder = builder;
builder = this.builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
rootNames,
compilerOptions,
host,
this.builder,
);
} else {
// When not in watch mode, the startup cost of the incremental analysis can be avoided by
// using an abstract builder that only wraps a TypeScript program.
builder = ts.createAbstractBuilder(rootNames, compilerOptions, host);
}

const diagnostics = [
Expand Down

0 comments on commit 4f2df00

Please sign in to comment.