Skip to content

Commit d31ff5e

Browse files
committed
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.
1 parent 6b20983 commit d31ff5e

1 file changed

Lines changed: 20 additions & 10 deletions

File tree

packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export function createCompilerPlugin(
7575
// eslint-disable-next-line max-lines-per-function
7676
async setup(build: PluginBuild): Promise<void> {
7777
let setupWarnings: PartialMessage[] | undefined = [];
78+
let diagnosticsPromise: ReturnType<AngularCompilation['diagnoseFiles']> | undefined;
7879
const preserveSymlinks = build.initialOptions.preserveSymlinks;
7980

8081
// Initialize a worker pool for JavaScript transformations.
@@ -402,15 +403,10 @@ export function createCompilerPlugin(
402403
}
403404
}
404405

405-
const diagnostics = await compilation.diagnoseFiles(
406-
useTypeChecking ? DiagnosticModes.All : DiagnosticModes.All & ~DiagnosticModes.Semantic,
407-
);
408-
if (diagnostics.errors?.length) {
409-
(result.errors ??= []).push(...diagnostics.errors);
410-
}
411-
if (diagnostics.warnings?.length) {
412-
(result.warnings ??= []).push(...diagnostics.warnings);
413-
}
406+
const diagnosticModes = useTypeChecking
407+
? DiagnosticModes.All
408+
: DiagnosticModes.All & ~DiagnosticModes.Semantic;
409+
diagnosticsPromise = compilation.diagnoseFiles(diagnosticModes);
414410

415411
// Add errors from failed additional results.
416412
// This must be done after emit to capture latest web worker results.
@@ -617,7 +613,7 @@ export function createCompilerPlugin(
617613
);
618614
}
619615

620-
build.onEnd((result) => {
616+
build.onEnd(async (result) => {
621617
// Ensure other compilations are unblocked if the main compilation throws during start
622618
if (angularCompilationContext.isPrimary()) {
623619
angularCompilationContext.markAsReady(hasCompilationErrors);
@@ -640,6 +636,20 @@ export function createCompilerPlugin(
640636
}
641637

642638
logCumulativeDurations();
639+
640+
if (diagnosticsPromise) {
641+
try {
642+
const diagnostics = await diagnosticsPromise;
643+
const errors = diagnostics.errors?.length ? diagnostics.errors : undefined;
644+
const warnings = diagnostics.warnings?.length ? diagnostics.warnings : undefined;
645+
646+
if (errors || warnings) {
647+
return { errors, warnings };
648+
}
649+
} finally {
650+
diagnosticsPromise = undefined;
651+
}
652+
}
643653
});
644654

645655
build.onDispose(() => {

0 commit comments

Comments
 (0)