From d7400206cd4c60105dc9266a8b8e80afbd45a79f Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Fri, 19 Jun 2026 12:30:41 +0000 Subject: [PATCH] fix(@angular/build): aggregate parallel worker performance timings on the main thread Rather than having the parallel worker thread print its cumulative durations separately to the console (which causes console spam and disjointed/incomplete final logs), we serialize and return the worker's durations to the main thread upon completing the diagnostics task. The main thread then merges them into the global cumulative durations map, producing a single, complete, and perfectly aggregated performance report at the end of the build. --- .../compilation/parallel-compilation.ts | 10 +++++-- .../angular/compilation/parallel-worker.ts | 8 +++++- .../build/src/tools/esbuild/profiling.ts | 26 +++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts b/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts index be612cbfcad4..a0dd58179ba2 100644 --- a/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts @@ -12,6 +12,7 @@ import { createRequire } from 'node:module'; import { MessageChannel } from 'node:worker_threads'; import type { SourceFile } from 'typescript'; import { WorkerPool } from '../../../utils/worker-pool'; +import { mergeCumulativeDurations } from '../../esbuild/profiling'; import type { AngularHostOptions } from '../angular-host'; import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation'; @@ -124,10 +125,15 @@ export class ParallelCompilation extends AngularCompilation { throw new Error('Not implemented in ParallelCompilation.'); } - override diagnoseFiles( + override async diagnoseFiles( modes = DiagnosticModes.All, ): Promise<{ errors?: PartialMessage[]; warnings?: PartialMessage[] }> { - return this.#worker.run(modes, { name: 'diagnose' }); + const { timings, ...result } = await this.#worker.run(modes, { name: 'diagnose' }); + if (timings) { + mergeCumulativeDurations(timings); + } + + return result; } override emitAffectedFiles(): Promise> { diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts index b6eccf20e3db..0a96ae74547a 100644 --- a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts +++ b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts @@ -11,6 +11,7 @@ import assert from 'node:assert'; import { randomUUID } from 'node:crypto'; import { type MessagePort, receiveMessageOnPort } from 'node:worker_threads'; import { SourceFileCache } from '../../esbuild/angular/source-file-cache'; +import { getAndClearCumulativeDurations } from '../../esbuild/profiling'; import type { AngularCompilation, DiagnosticModes } from './angular-compilation'; import { AotCompilation } from './aot-compilation'; import { JitCompilation } from './jit-compilation'; @@ -121,12 +122,17 @@ export async function initialize(request: InitRequest) { export async function diagnose(modes: DiagnosticModes): Promise<{ errors?: PartialMessage[]; warnings?: PartialMessage[]; + timings?: Record; }> { assert(compilation); const diagnostics = await compilation.diagnoseFiles(modes); + const timings = getAndClearCumulativeDurations(); - return diagnostics; + return { + ...diagnostics, + timings, + }; } export async function emit() { diff --git a/packages/angular/build/src/tools/esbuild/profiling.ts b/packages/angular/build/src/tools/esbuild/profiling.ts index 2e67a4cb27f2..80fd26c32d0e 100644 --- a/packages/angular/build/src/tools/esbuild/profiling.ts +++ b/packages/angular/build/src/tools/esbuild/profiling.ts @@ -14,6 +14,32 @@ export function resetCumulativeDurations(): void { cumulativeDurations?.clear(); } +export function getAndClearCumulativeDurations(): Record | undefined { + if (!cumulativeDurations || cumulativeDurations.size === 0) { + return undefined; + } + + const data = Object.fromEntries(cumulativeDurations); + + cumulativeDurations.clear(); + + return data; +} + +export function mergeCumulativeDurations(data: Record): void { + cumulativeDurations ??= new Map(); + + for (const [name, durations] of Object.entries(data)) { + let existing = cumulativeDurations.get(name); + if (!existing) { + existing = []; + cumulativeDurations.set(name, existing); + } + + existing.push(...durations); + } +} + export function logCumulativeDurations(): void { if (!debugPerformance || !cumulativeDurations) { return;