Skip to content

Commit aef5a98

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 ebdf495 commit aef5a98

1 file changed

Lines changed: 32 additions & 10 deletions

File tree

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

Lines changed: 32 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.
@@ -157,6 +158,7 @@ export function createCompilerPlugin(
157158

158159
// eslint-disable-next-line max-lines-per-function
159160
build.onStart(async () => {
161+
hasCompilationErrors = true;
160162
await initializeHash();
161163

162164
const result: OnStartResult = {
@@ -402,15 +404,21 @@ export function createCompilerPlugin(
402404
}
403405
}
404406

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-
}
407+
const diagnosticModes = useTypeChecking
408+
? DiagnosticModes.All
409+
: DiagnosticModes.All & ~DiagnosticModes.Semantic;
410+
diagnosticsPromise = compilation.diagnoseFiles(diagnosticModes).catch((error) => ({
411+
errors: [
412+
{
413+
text: 'Angular compilation diagnostics failed.',
414+
notes: [
415+
{
416+
text: error instanceof Error ? (error.stack ?? error.message) : String(error),
417+
},
418+
],
419+
},
420+
],
421+
}));
414422

415423
// Add errors from failed additional results.
416424
// This must be done after emit to capture latest web worker results.
@@ -617,7 +625,7 @@ export function createCompilerPlugin(
617625
);
618626
}
619627

620-
build.onEnd((result) => {
628+
build.onEnd(async (result) => {
621629
// Ensure other compilations are unblocked if the main compilation throws during start
622630
if (angularCompilationContext.isPrimary()) {
623631
angularCompilationContext.markAsReady(hasCompilationErrors);
@@ -640,6 +648,20 @@ export function createCompilerPlugin(
640648
}
641649

642650
logCumulativeDurations();
651+
652+
if (diagnosticsPromise) {
653+
try {
654+
const diagnostics = await diagnosticsPromise;
655+
const errors = diagnostics.errors?.length ? diagnostics.errors : undefined;
656+
const warnings = diagnostics.warnings?.length ? diagnostics.warnings : undefined;
657+
658+
if (errors || warnings) {
659+
return { errors, warnings };
660+
}
661+
} finally {
662+
diagnosticsPromise = undefined;
663+
}
664+
}
643665
});
644666

645667
build.onDispose(() => {

0 commit comments

Comments
 (0)