diff --git a/packages/ngtools/webpack/src/ivy/plugin.ts b/packages/ngtools/webpack/src/ivy/plugin.ts index ca6cc087ac3d..eee83529b6fa 100644 --- a/packages/ngtools/webpack/src/ivy/plugin.ts +++ b/packages/ngtools/webpack/src/ivy/plugin.ts @@ -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; @@ -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(); - // 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 @@ -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 = [