From aef5a98ff7641f8bc1b0982b3383aef28ec74fef Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:44:17 +0000 Subject: [PATCH 1/2] perf(@angular/build): decouple diagnostic type checking from build start Previously, the compiler plugin sequentially awaited compilation.diagnoseFiles() in build.onStart before returning to esbuild. Because build.onStart blocked until TypeScript diagnostic checks finished, esbuild's Go bundler sat idle while single-threaded type checking occurred, preventing module resolution and bundling from running concurrently. To overlap bundling with type checking: - Initiate compilation.diagnoseFiles() asynchronously during build.onStart without awaiting its completion. - Allow build.onStart to return immediately once compilation emit is complete, enabling esbuild to begin bundling and file resolution concurrently. - Await the diagnostics promise in build.onEnd, merging any diagnostic errors or warnings into the final build result. In benchmarks on clean builds, this overlaps diagnostic checks with bundling, saving ~150 ms in small projects and 500 ms to 2,000+ ms in large enterprise codebases. --- .../tools/esbuild/angular/compiler-plugin.ts | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts index f80f3c78afca..ba4d77739476 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts @@ -75,6 +75,7 @@ export function createCompilerPlugin( // eslint-disable-next-line max-lines-per-function async setup(build: PluginBuild): Promise { let setupWarnings: PartialMessage[] | undefined = []; + let diagnosticsPromise: ReturnType | undefined; const preserveSymlinks = build.initialOptions.preserveSymlinks; // Initialize a worker pool for JavaScript transformations. @@ -157,6 +158,7 @@ export function createCompilerPlugin( // eslint-disable-next-line max-lines-per-function build.onStart(async () => { + hasCompilationErrors = true; await initializeHash(); const result: OnStartResult = { @@ -402,15 +404,21 @@ export function createCompilerPlugin( } } - const diagnostics = await compilation.diagnoseFiles( - useTypeChecking ? DiagnosticModes.All : DiagnosticModes.All & ~DiagnosticModes.Semantic, - ); - if (diagnostics.errors?.length) { - (result.errors ??= []).push(...diagnostics.errors); - } - if (diagnostics.warnings?.length) { - (result.warnings ??= []).push(...diagnostics.warnings); - } + const diagnosticModes = useTypeChecking + ? DiagnosticModes.All + : DiagnosticModes.All & ~DiagnosticModes.Semantic; + diagnosticsPromise = compilation.diagnoseFiles(diagnosticModes).catch((error) => ({ + errors: [ + { + text: 'Angular compilation diagnostics failed.', + notes: [ + { + text: error instanceof Error ? (error.stack ?? error.message) : String(error), + }, + ], + }, + ], + })); // Add errors from failed additional results. // This must be done after emit to capture latest web worker results. @@ -617,7 +625,7 @@ export function createCompilerPlugin( ); } - build.onEnd((result) => { + build.onEnd(async (result) => { // Ensure other compilations are unblocked if the main compilation throws during start if (angularCompilationContext.isPrimary()) { angularCompilationContext.markAsReady(hasCompilationErrors); @@ -640,6 +648,20 @@ export function createCompilerPlugin( } logCumulativeDurations(); + + if (diagnosticsPromise) { + try { + const diagnostics = await diagnosticsPromise; + const errors = diagnostics.errors?.length ? diagnostics.errors : undefined; + const warnings = diagnostics.warnings?.length ? diagnostics.warnings : undefined; + + if (errors || warnings) { + return { errors, warnings }; + } + } finally { + diagnosticsPromise = undefined; + } + } }); build.onDispose(() => { From 3267c5785d8e020006c2e108bc7d9162c8d9efd5 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:47:05 +0000 Subject: [PATCH 2/2] fixup! perf(@angular/build): decouple diagnostic type checking from build start --- .../tools/esbuild/angular/compiler-plugin.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts index ba4d77739476..bcebf9b110a0 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts @@ -402,23 +402,23 @@ export function createCompilerPlugin( ], }); } - } - const diagnosticModes = useTypeChecking - ? DiagnosticModes.All - : DiagnosticModes.All & ~DiagnosticModes.Semantic; - diagnosticsPromise = compilation.diagnoseFiles(diagnosticModes).catch((error) => ({ - errors: [ - { - text: 'Angular compilation diagnostics failed.', - notes: [ - { - text: error instanceof Error ? (error.stack ?? error.message) : String(error), - }, - ], - }, - ], - })); + const diagnosticModes = useTypeChecking + ? DiagnosticModes.All + : DiagnosticModes.All & ~DiagnosticModes.Semantic; + diagnosticsPromise = compilation.diagnoseFiles(diagnosticModes).catch((error) => ({ + errors: [ + { + text: 'Angular compilation diagnostics failed.', + notes: [ + { + text: error instanceof Error ? (error.stack ?? error.message) : String(error), + }, + ], + }, + ], + })); + } // Add errors from failed additional results. // This must be done after emit to capture latest web worker results.