From 67a1fd33753abfc1cbd62b57a0064d54644870a6 Mon Sep 17 00:00:00 2001 From: Aukevanoost Date: Wed, 10 Dec 2025 08:24:27 +0100 Subject: [PATCH] fix(@angular/build): added JavaScriptTransformer close to dispose method This change allows for awaiting the disposed plugin and added the missing close method of the JavaScript transformer to the onDispose. When the plugin is run multiple times on different projects/bundles without proper closing it will result in a node heap corruption. This allows us to cleanup the build before we start a new one. Closes #31973 --- packages/angular/build/src/private.ts | 1 + .../tools/esbuild/angular/compilation-state.ts | 16 ++++++++++++++++ .../src/tools/esbuild/angular/compiler-plugin.ts | 13 ++++++++----- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/angular/build/src/private.ts b/packages/angular/build/src/private.ts index 175dd39530ae..38cfc3a07181 100644 --- a/packages/angular/build/src/private.ts +++ b/packages/angular/build/src/private.ts @@ -59,6 +59,7 @@ export function createCompilerPlugin( ), ); } +export { getSharedCompilationStateCleanup } from './tools/esbuild/angular/compilation-state'; export type { AngularCompilation } from './tools/angular/compilation'; export { DiagnosticModes } from './tools/angular/compilation'; diff --git a/packages/angular/build/src/tools/esbuild/angular/compilation-state.ts b/packages/angular/build/src/tools/esbuild/angular/compilation-state.ts index 79b46313f1ec..a9e190e15a7e 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compilation-state.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compilation-state.ts @@ -10,6 +10,17 @@ export class SharedTSCompilationState { #pendingCompilation = true; #resolveCompilationReady: ((value: boolean) => void) | undefined; #compilationReadyPromise: Promise | undefined; + + /** + * ESbuild doesnt allow for awaiting the cleanup of plugins, therefore this + * allows consumers of the compiler-plugin to await the disposal of the plugin + * after ESbuild dispose function was called. + */ + #disposeCompilation: (() => void) | undefined; + awaitCompilationDisposed: Promise = new Promise((resolve) => { + this.#disposeCompilation = resolve; + }); + #hasErrors = true; get waitUntilReady(): Promise { @@ -37,6 +48,7 @@ export class SharedTSCompilationState { dispose(): void { this.markAsReady(true); + this.#disposeCompilation?.(); globalSharedCompilationState = undefined; } } @@ -46,3 +58,7 @@ let globalSharedCompilationState: SharedTSCompilationState | undefined; export function getSharedCompilationState(): SharedTSCompilationState { return (globalSharedCompilationState ??= new SharedTSCompilationState()); } + +export function getSharedCompilationStateCleanup() { + return globalSharedCompilationState?.awaitCompilationDisposed ?? Promise.resolve(); +} 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 95e6694b728b..a8af37814b79 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts @@ -588,11 +588,14 @@ export function createCompilerPlugin( logCumulativeDurations(); }); - build.onDispose(() => { - sharedTSCompilationState?.dispose(); - void compilation.close?.(); - void cacheStore?.close(); - }); + build.onDispose( + () => + void Promise.all( + [compilation?.close?.(), cacheStore?.close(), javascriptTransformer.close()].filter( + Boolean, + ), + ).then(() => sharedTSCompilationState?.dispose()), + ); /** * Checks if the file has side-effects when `advancedOptimizations` is enabled.