Skip to content

Commit cbc02f9

Browse files
committed
refactor(@angular/build): introduce AngularCompilationContext to manage compiler lifecycle
Refactors the ambient/global sharedTSCompilationState synchronization singleton into an explicit, orchestrator-instantiated AngularCompilationContext. This context encapsulates the AngularCompilation instance and its ready promise/resolver. The context is instantiated once in executeBuild and passed down to both browser and server compiler plugins, eliminating global state and providing a single cohesive API to manage worker thread lifecycles via context.dispose().
1 parent b9681c0 commit cbc02f9

5 files changed

Lines changed: 53 additions & 33 deletions

File tree

packages/angular/build/src/builders/application/execute-build.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import { BuilderContext } from '@angular-devkit/architect';
1010
import { createAngularCompilation } from '../../tools/angular/compilation';
11+
import { AngularCompilationContext } from '../../tools/esbuild/angular/compilation-state';
1112
import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
1213
import { generateBudgetStats } from '../../tools/esbuild/budget-stats';
1314
import { BundleContextResult, BundlerContext } from '../../tools/esbuild/bundler-context';
@@ -102,13 +103,16 @@ export async function executeBuild(
102103
if (options.templateUpdates) {
103104
templateUpdates = new Map<string, string>();
104105
}
106+
const angularCompilation = await createAngularCompilation(
107+
!!options.jit,
108+
!options.serverEntryPoint,
109+
);
105110
bundlerContexts = setupBundlerContexts(
106111
options,
107112
target,
108113
codeBundleCache,
109114
componentStyleBundler,
110-
// Create new reusable compilation for the appropriate mode based on the `jit` plugin option
111-
await createAngularCompilation(!!options.jit, !options.serverEntryPoint),
115+
new AngularCompilationContext(angularCompilation),
112116
templateUpdates,
113117
);
114118

packages/angular/build/src/builders/application/setup-bundling.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { AngularCompilation } from '../../tools/angular/compilation';
9+
import type { AngularCompilationContext } from '../../tools/esbuild/angular/compilation-state';
1010
import { ComponentStylesheetBundler } from '../../tools/esbuild/angular/component-stylesheets';
11-
import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
11+
import type { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
1212
import {
1313
createBrowserCodeBundleOptions,
1414
createBrowserPolyfillBundleOptions,
@@ -20,7 +20,7 @@ import { BundlerContext } from '../../tools/esbuild/bundler-context';
2020
import { createGlobalScriptsBundleOptions } from '../../tools/esbuild/global-scripts';
2121
import { createGlobalStylesBundleOptions } from '../../tools/esbuild/global-styles';
2222
import { getSupportedNodeTargets } from '../../tools/esbuild/utils';
23-
import { NormalizedApplicationBuildOptions } from './options';
23+
import type { NormalizedApplicationBuildOptions } from './options';
2424

2525
/**
2626
* Generates one or more BundlerContext instances based on the builder provided
@@ -35,7 +35,7 @@ export function setupBundlerContexts(
3535
target: string[],
3636
codeBundleCache: SourceFileCache,
3737
stylesheetBundler: ComponentStylesheetBundler,
38-
angularCompilation: AngularCompilation,
38+
angularCompilationContext: AngularCompilationContext,
3939
templateUpdates: Map<string, string> | undefined,
4040
): {
4141
typescriptContexts: BundlerContext[];
@@ -63,7 +63,7 @@ export function setupBundlerContexts(
6363
target,
6464
codeBundleCache,
6565
stylesheetBundler,
66-
angularCompilation,
66+
angularCompilationContext,
6767
templateUpdates,
6868
),
6969
),

packages/angular/build/src/tools/esbuild/angular/compilation-state.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,23 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
export class SharedTSCompilationState {
9+
import type { AngularCompilation } from '../../angular/compilation';
10+
11+
export class AngularCompilationContext {
12+
#compilation: AngularCompilation;
1013
#pendingCompilation = true;
1114
#resolveCompilationReady: ((value: boolean) => void) | undefined;
1215
#compilationReadyPromise: Promise<boolean> | undefined;
1316
#hasErrors = true;
1417

18+
constructor(compilation: AngularCompilation) {
19+
this.#compilation = compilation;
20+
}
21+
22+
get compilation(): AngularCompilation {
23+
return this.#compilation;
24+
}
25+
1526
get waitUntilReady(): Promise<boolean> {
1627
if (!this.#pendingCompilation) {
1728
return Promise.resolve(this.#hasErrors);
@@ -35,14 +46,14 @@ export class SharedTSCompilationState {
3546
this.#pendingCompilation = true;
3647
}
3748

38-
dispose(): void {
49+
#disposed = false;
50+
51+
async dispose(): Promise<void> {
52+
if (this.#disposed) {
53+
return;
54+
}
55+
this.#disposed = true;
3956
this.markAsReady(true);
40-
globalSharedCompilationState = undefined;
57+
await this.#compilation.close?.();
4158
}
4259
}
43-
44-
let globalSharedCompilationState: SharedTSCompilationState | undefined;
45-
46-
export function getSharedCompilationState(): SharedTSCompilationState {
47-
return (globalSharedCompilationState ??= new SharedTSCompilationState());
48-
}

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

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { AngularCompilation, DiagnosticModes, NoopCompilation } from '../../angu
2727
import { JavaScriptTransformer } from '../javascript-transformer';
2828
import { LoadResultCache, createCachedLoad } from '../load-result-cache';
2929
import { logCumulativeDurations, profileAsync, resetCumulativeDurations } from '../profiling';
30-
import { SharedTSCompilationState, getSharedCompilationState } from './compilation-state';
30+
import { AngularCompilationContext } from './compilation-state';
3131
import { ComponentStylesheetBundler } from './component-stylesheets';
3232
import { FileReferenceTracker } from './file-reference-tracker';
3333
import { setupJitPluginCallbacks } from './jit-plugin-callbacks';
@@ -61,7 +61,10 @@ export interface CompilerPluginOptions {
6161
// eslint-disable-next-line max-lines-per-function
6262
export function createCompilerPlugin(
6363
pluginOptions: CompilerPluginOptions,
64-
compilationOrFactory: AngularCompilation | (() => Promise<AngularCompilation>),
64+
compilationContextOrCompilation:
65+
| AngularCompilationContext
66+
| AngularCompilation
67+
| (() => Promise<AngularCompilation>),
6568
stylesheetBundler: ComponentStylesheetBundler,
6669
): Plugin {
6770
return {
@@ -111,10 +114,15 @@ export function createCompilerPlugin(
111114

112115
// The factory is only relevant for compatibility purposes with the private API.
113116
// TODO: Update private API in the next major to allow compilation function factory removal here.
114-
const compilation =
115-
typeof compilationOrFactory === 'function'
116-
? await compilationOrFactory()
117-
: compilationOrFactory;
117+
const angularCompilationContext =
118+
compilationContextOrCompilation instanceof AngularCompilationContext
119+
? compilationContextOrCompilation
120+
: new AngularCompilationContext(
121+
typeof compilationContextOrCompilation === 'function'
122+
? await compilationContextOrCompilation()
123+
: compilationContextOrCompilation,
124+
);
125+
const compilation = angularCompilationContext.compilation;
118126

119127
// The in-memory cache of TypeScript file outputs will be used during the build in `onLoad` callbacks for TS files.
120128
// A string value indicates direct TS/NG output and a Uint8Array indicates fully transformed code.
@@ -136,16 +144,13 @@ export function createCompilerPlugin(
136144
// Determines if transpilation should be handle by TypeScript or esbuild
137145
let useTypeScriptTranspilation = true;
138146

139-
let sharedTSCompilationState: SharedTSCompilationState | undefined;
140-
141147
// To fully invalidate files, track resource referenced files and their referencing source
142148
const referencedFileTracker = new FileReferenceTracker();
143149

144150
// eslint-disable-next-line max-lines-per-function
145151
build.onStart(async () => {
146-
sharedTSCompilationState = getSharedCompilationState();
147152
if (!(compilation instanceof NoopCompilation)) {
148-
sharedTSCompilationState.markAsInProgress();
153+
angularCompilationContext.markAsInProgress();
149154
}
150155

151156
const result: OnStartResult = {
@@ -348,7 +353,7 @@ export function createCompilerPlugin(
348353
}
349354

350355
if (compilation instanceof NoopCompilation) {
351-
hasCompilationErrors = await sharedTSCompilationState.waitUntilReady;
356+
hasCompilationErrors = await angularCompilationContext.waitUntilReady;
352357

353358
return result;
354359
}
@@ -417,7 +422,7 @@ export function createCompilerPlugin(
417422
// Reset the setup warnings so that they are only shown during the first build.
418423
setupWarnings = undefined;
419424

420-
sharedTSCompilationState.markAsReady(hasCompilationErrors);
425+
angularCompilationContext.markAsReady(hasCompilationErrors);
421426

422427
return result;
423428
});
@@ -576,7 +581,7 @@ export function createCompilerPlugin(
576581

577582
build.onEnd((result) => {
578583
// Ensure other compilations are unblocked if the main compilation throws during start
579-
sharedTSCompilationState?.markAsReady(hasCompilationErrors);
584+
angularCompilationContext.markAsReady(hasCompilationErrors);
580585

581586
for (const { outputFiles, metafile } of additionalResults.values()) {
582587
// Add any additional output files to the main output files
@@ -598,8 +603,7 @@ export function createCompilerPlugin(
598603
});
599604

600605
build.onDispose(() => {
601-
sharedTSCompilationState?.dispose();
602-
void compilation.close?.();
606+
void angularCompilationContext.dispose();
603607
void javascriptTransformer.close();
604608
void cacheStore?.close();
605609
});

packages/angular/build/src/tools/esbuild/application-code-bundle.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
SERVER_GENERATED_EXTERNALS,
2121
} from '../../utils/server-rendering/manifest';
2222
import { AngularCompilation, NoopCompilation } from '../angular/compilation';
23+
import { AngularCompilationContext } from './angular/compilation-state';
2324
import { createCompilerPlugin } from './angular/compiler-plugin';
2425
import { ComponentStylesheetBundler } from './angular/component-stylesheets';
2526
import { SourceFileCache } from './angular/source-file-cache';
@@ -42,7 +43,7 @@ export function createBrowserCodeBundleOptions(
4243
target: string[],
4344
sourceFileCache: SourceFileCache,
4445
stylesheetBundler: ComponentStylesheetBundler,
45-
angularCompilation: AngularCompilation,
46+
angularCompilationContext: AngularCompilationContext,
4647
templateUpdates: Map<string, string> | undefined,
4748
): BundlerOptionsFactory {
4849
return (loadCache) => {
@@ -77,7 +78,7 @@ export function createBrowserCodeBundleOptions(
7778
createCompilerPlugin(
7879
// JS/TS options
7980
pluginOptions,
80-
angularCompilation,
81+
angularCompilationContext,
8182
// Component stylesheet bundler
8283
stylesheetBundler,
8384
),

0 commit comments

Comments
 (0)