Skip to content

Commit 4f2df00

Browse files
clydinalan-agius4
authored andcommitted
perf(@ngtools/webpack): reduce non-watch mode TypeScript diagnostic analysis 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.
1 parent 1080a52 commit 4f2df00

File tree

1 file changed

+56
-47
lines changed

1 file changed

+56
-47
lines changed

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

+56-47
Original file line numberDiff line numberDiff line change
@@ -377,10 +377,11 @@ export class AngularWebpackPlugin {
377377
}
378378

379379
private loadConfiguration(compilation: WebpackCompilation) {
380-
const { options: compilerOptions, rootNames, errors } = readConfiguration(
381-
this.pluginOptions.tsconfig,
382-
this.pluginOptions.compilerOptions,
383-
);
380+
const {
381+
options: compilerOptions,
382+
rootNames,
383+
errors,
384+
} = readConfiguration(this.pluginOptions.tsconfig, this.pluginOptions.compilerOptions);
384385
compilerOptions.enableIvy = true;
385386
compilerOptions.noEmitOnError = false;
386387
compilerOptions.suppressOutputPathCheck = true;
@@ -425,51 +426,57 @@ export class AngularWebpackPlugin {
425426
const typeScriptProgram = angularProgram.getTsProgram();
426427
augmentProgramWithVersioning(typeScriptProgram);
427428

428-
const builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
429-
typeScriptProgram,
430-
host,
431-
this.builder,
432-
);
433-
434-
// Save for next rebuild
429+
let builder: ts.BuilderProgram | ts.EmitAndSemanticDiagnosticsBuilderProgram;
435430
if (this.watchMode) {
436-
this.builder = builder;
431+
builder = this.builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
432+
typeScriptProgram,
433+
host,
434+
this.builder,
435+
);
437436
this.ngtscNextProgram = angularProgram;
437+
} else {
438+
// When not in watch mode, the startup cost of the incremental analysis can be avoided by
439+
// using an abstract builder that only wraps a TypeScript program.
440+
builder = ts.createAbstractBuilder(typeScriptProgram, host);
438441
}
439442

440443
// Update semantic diagnostics cache
441444
const affectedFiles = new Set<ts.SourceFile>();
442-
// eslint-disable-next-line no-constant-condition
443-
while (true) {
444-
const result = builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => {
445-
// If the affected file is a TTC shim, add the shim's original source file.
446-
// This ensures that changes that affect TTC are typechecked even when the changes
447-
// are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes.
448-
// For example, changing @Input property types of a directive used in another component's
449-
// template.
450-
if (
451-
ignoreForDiagnostics.has(sourceFile) &&
452-
sourceFile.fileName.endsWith('.ngtypecheck.ts')
453-
) {
454-
// This file name conversion relies on internal compiler logic and should be converted
455-
// to an official method when available. 15 is length of `.ngtypecheck.ts`
456-
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
457-
const originalSourceFile = builder.getSourceFile(originalFilename);
458-
if (originalSourceFile) {
459-
affectedFiles.add(originalSourceFile);
445+
446+
// Analyze affected files when in watch mode for incremental type checking
447+
if ('getSemanticDiagnosticsOfNextAffectedFile' in builder) {
448+
// eslint-disable-next-line no-constant-condition
449+
while (true) {
450+
const result = builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => {
451+
// If the affected file is a TTC shim, add the shim's original source file.
452+
// This ensures that changes that affect TTC are typechecked even when the changes
453+
// are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes.
454+
// For example, changing @Input property types of a directive used in another component's
455+
// template.
456+
if (
457+
ignoreForDiagnostics.has(sourceFile) &&
458+
sourceFile.fileName.endsWith('.ngtypecheck.ts')
459+
) {
460+
// This file name conversion relies on internal compiler logic and should be converted
461+
// to an official method when available. 15 is length of `.ngtypecheck.ts`
462+
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
463+
const originalSourceFile = builder.getSourceFile(originalFilename);
464+
if (originalSourceFile) {
465+
affectedFiles.add(originalSourceFile);
466+
}
467+
468+
return true;
460469
}
461470

462-
return true;
463-
}
471+
return false;
472+
});
464473

465-
return false;
466-
});
474+
if (!result) {
475+
break;
476+
}
467477

468-
if (!result) {
469-
break;
478+
affectedFiles.add(result.affected as ts.SourceFile);
470479
}
471-
472-
affectedFiles.add(result.affected as ts.SourceFile);
473480
}
474481

475482
// Collect non-semantic diagnostics
@@ -581,16 +588,18 @@ export class AngularWebpackPlugin {
581588
host: CompilerHost,
582589
diagnosticsReporter: DiagnosticsReporter,
583590
) {
584-
const builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
585-
rootNames,
586-
compilerOptions,
587-
host,
588-
this.builder,
589-
);
590-
591-
// Save for next rebuild
591+
let builder;
592592
if (this.watchMode) {
593-
this.builder = builder;
593+
builder = this.builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
594+
rootNames,
595+
compilerOptions,
596+
host,
597+
this.builder,
598+
);
599+
} else {
600+
// When not in watch mode, the startup cost of the incremental analysis can be avoided by
601+
// using an abstract builder that only wraps a TypeScript program.
602+
builder = ts.createAbstractBuilder(rootNames, compilerOptions, host);
594603
}
595604

596605
const diagnostics = [

0 commit comments

Comments
 (0)