Skip to content

Commit f329174

Browse files
committed
refactor(@angular/build): eliminate NoopCompilation and introduce primary and secondary compilation contexts
NoopCompilation previously served as a AngularCompilation placeholder to satisfy the compilation requirement on AngularCompilationContext in secondary contexts (such as polyfills and server main code), leading to redundant compiler option extraction and tsconfig loading. AngularCompilationContext is now an abstract base class defining the shared contract. PrimaryCompilationContext encapsulates the active AngularCompilation, manages the lifecycle state, and holds the resolved compiler options. SecondaryCompilationContext omits the compilation entirely, delegates readiness and compiler option resolution to the primary context, and provides no-op lifecycle management. Secondary builds now await primary completion and retrieve compiler options without initializing or invoking a separate compilation.
1 parent 2dc9aae commit f329174

8 files changed

Lines changed: 280 additions & 137 deletions

File tree

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

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

99
import { BuilderContext } from '@angular-devkit/architect';
1010
import { createAngularCompilation } from '../../tools/angular/compilation';
11-
import { AngularCompilationContext } from '../../tools/esbuild/angular/compilation-state';
11+
import {
12+
AngularCompilationContext,
13+
PrimaryCompilationContext,
14+
} from '../../tools/esbuild/angular/compilation-state';
1215
import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
1316
import { generateBudgetStats } from '../../tools/esbuild/budget-stats';
1417
import { BundleContextResult, BundlerContext } from '../../tools/esbuild/bundler-context';
@@ -125,7 +128,7 @@ export async function executeBuild(
125128
!!options.jit,
126129
!options.serverEntryPoint,
127130
);
128-
angularCompilationContext = new AngularCompilationContext(angularCompilation);
131+
angularCompilationContext = new PrimaryCompilationContext(angularCompilation);
129132
bundlerContexts = setupBundlerContexts(
130133
options,
131134
target,

packages/angular/build/src/private.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
* their existence may change in any future version.
1414
*/
1515

16-
import { NoopCompilation, createAngularCompilation } from './tools/angular/compilation';
16+
import { createAngularCompilation } from './tools/angular/compilation';
17+
import { SecondaryCompilationContext } from './tools/esbuild/angular/compilation-state';
1718
import {
1819
CompilerPluginOptions,
1920
createCompilerPlugin as internalCreateCompilerPlugin,
@@ -52,7 +53,7 @@ export function createCompilerPlugin(
5253
return internalCreateCompilerPlugin(
5354
pluginOptions,
5455
pluginOptions.noopTypeScriptCompilation
55-
? new NoopCompilation()
56+
? new SecondaryCompilationContext()
5657
: () => createAngularCompilation(!!pluginOptions.jit, !!pluginOptions.browserOnlyBuild),
5758
new ComponentStylesheetBundler(
5859
styleOptions,

packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
AngularCompilation,
1515
AngularCompilationResult,
1616
DiagnosticModes,
17-
NoopCompilation,
1817
createAngularCompilation,
1918
} from './index';
2019

@@ -59,44 +58,6 @@ describe('AngularCompilation', () => {
5958
expect(diagnostics).toEqual({});
6059
});
6160

62-
describe('NoopCompilation', () => {
63-
it('initializes with empty referencedFiles and compiler options', async () => {
64-
const compilation = new NoopCompilation();
65-
const mockHostOptions = {} as AngularHostOptions;
66-
const result = await compilation.initialize('tsconfig.json', mockHostOptions);
67-
68-
expect(result.referencedFiles).toEqual([]);
69-
expect(result.compilerOptions).toBeDefined();
70-
});
71-
72-
it('initializes with CompilerOptionOverrides object', async () => {
73-
const compilation = new NoopCompilation();
74-
const mockHostOptions = {} as AngularHostOptions;
75-
const result = await compilation.initialize('tsconfig.json', mockHostOptions, {
76-
sourcemap: true,
77-
enableHmr: true,
78-
});
79-
80-
expect(result.referencedFiles).toEqual([]);
81-
expect(result.compilerOptions.inlineSources).toBe(true);
82-
expect(result.compilerOptions.inlineSourceMap).toBe(true);
83-
expect(result.compilerOptions['_enableHmr']).toBe(true);
84-
});
85-
86-
it('throws when calling emitAffectedFiles', () => {
87-
const compilation = new NoopCompilation();
88-
expect(() => compilation.emitAffectedFiles()).toThrowError(
89-
'Not available when using noop compilation.',
90-
);
91-
});
92-
93-
it('returns empty diagnostics from diagnoseFiles', async () => {
94-
const compilation = new NoopCompilation();
95-
const diagnostics = await compilation.diagnoseFiles();
96-
expect(diagnostics).toEqual({});
97-
});
98-
});
99-
10061
describe('TypeScriptCompilation', () => {
10162
class MockTypeScriptCompilation extends TypeScriptCompilation {
10263
async initialize(): Promise<AngularCompilationResult> {

packages/angular/build/src/tools/angular/compilation/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,3 @@ export {
1616
} from './angular-compilation';
1717
export type { CompilerOptionOverrides } from './compiler-options';
1818
export { createAngularCompilation, type AngularCompilationMode } from './factory';
19-
export { NoopCompilation } from './noop-compilation';

packages/angular/build/src/tools/angular/compilation/noop-compilation.ts

Lines changed: 0 additions & 58 deletions
This file was deleted.

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

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

9-
import { type AngularCompilation, NoopCompilation } from '../../angular/compilation';
9+
import type { CompilerOptions } from '@angular/compiler-cli';
10+
import type { AngularCompilation } from '../../angular/compilation';
1011

11-
export class AngularCompilationContext {
12-
#compilation: AngularCompilation;
12+
export abstract class AngularCompilationContext {
13+
abstract readonly compilation?: AngularCompilation;
14+
abstract isPrimary(): this is PrimaryCompilationContext;
15+
abstract readonly waitUntilReady: Promise<boolean>;
16+
abstract getCompilerOptions(): Promise<CompilerOptions>;
17+
abstract dispose(): Promise<void>;
18+
19+
createSecondaryContext(): AngularCompilationContext {
20+
return new SecondaryCompilationContext(this);
21+
}
22+
}
23+
24+
export class PrimaryCompilationContext extends AngularCompilationContext {
25+
readonly #compilation: AngularCompilation;
1326
#pendingCompilation = true;
1427
#resolveCompilationReady: ((value: boolean) => void) | undefined;
1528
#compilationReadyPromise: Promise<boolean> | undefined;
1629
#hasErrors = true;
1730

31+
#compilerOptions: CompilerOptions | undefined;
32+
#resolveCompilerOptions: ((options: CompilerOptions) => void) | undefined;
33+
#compilerOptionsPromise: Promise<CompilerOptions> | undefined;
34+
1835
constructor(compilation: AngularCompilation) {
36+
super();
1937
this.#compilation = compilation;
2038
}
2139

22-
get compilation(): AngularCompilation {
40+
override isPrimary(): this is PrimaryCompilationContext {
41+
return true;
42+
}
43+
44+
override get compilation(): AngularCompilation {
2345
return this.#compilation;
2446
}
2547

26-
get waitUntilReady(): Promise<boolean> {
48+
override get waitUntilReady(): Promise<boolean> {
2749
if (!this.#pendingCompilation) {
2850
return Promise.resolve(this.#hasErrors);
2951
}
@@ -35,20 +57,51 @@ export class AngularCompilationContext {
3557
return this.#compilationReadyPromise;
3658
}
3759

60+
override getCompilerOptions(): Promise<CompilerOptions> {
61+
if (this.#compilerOptions) {
62+
return Promise.resolve(this.#compilerOptions);
63+
}
64+
65+
if (!this.#pendingCompilation) {
66+
return Promise.resolve({});
67+
}
68+
69+
this.#compilerOptionsPromise ??= new Promise((resolve) => {
70+
this.#resolveCompilerOptions = resolve;
71+
});
72+
73+
return this.#compilerOptionsPromise;
74+
}
75+
76+
setCompilerOptions(options: CompilerOptions): void {
77+
this.#compilerOptions = options;
78+
this.#resolveCompilerOptions?.(options);
79+
this.#resolveCompilerOptions = undefined;
80+
this.#compilerOptionsPromise = undefined;
81+
}
82+
3883
markAsReady(hasErrors: boolean): void {
3984
this.#hasErrors = hasErrors;
4085
this.#resolveCompilationReady?.(hasErrors);
86+
this.#resolveCompilationReady = undefined;
4187
this.#compilationReadyPromise = undefined;
4288
this.#pendingCompilation = false;
89+
90+
if (this.#resolveCompilerOptions) {
91+
this.#resolveCompilerOptions(this.#compilerOptions ?? {});
92+
this.#resolveCompilerOptions = undefined;
93+
this.#compilerOptionsPromise = undefined;
94+
}
4395
}
4496

4597
markAsInProgress(): void {
4698
this.#pendingCompilation = true;
99+
this.#compilerOptions = undefined;
47100
}
48101

49102
#disposal: Promise<void> | undefined;
50103

51-
dispose(): Promise<void> {
104+
override dispose(): Promise<void> {
52105
// Reuse any in progress disposal to ensure all callers can await completion
53106
return (this.#disposal ??= this.#close());
54107
}
@@ -61,27 +114,27 @@ export class AngularCompilationContext {
61114
// Suppress closure errors to avoid unhandled rejections during teardown.
62115
}
63116
}
117+
}
64118

65-
createSecondaryContext(): AngularCompilationContext {
66-
return new SecondaryCompilationContext(this);
119+
export class SecondaryCompilationContext extends AngularCompilationContext {
120+
constructor(private readonly primaryContext?: AngularCompilationContext) {
121+
super();
67122
}
68-
}
69123

70-
class SecondaryCompilationContext extends AngularCompilationContext {
71-
constructor(private primaryContext: AngularCompilationContext) {
72-
super(new NoopCompilation());
124+
override isPrimary(): this is PrimaryCompilationContext {
125+
return false;
73126
}
74127

75-
override get waitUntilReady(): Promise<boolean> {
76-
return this.primaryContext.waitUntilReady;
128+
override get compilation(): undefined {
129+
return undefined;
77130
}
78131

79-
override markAsReady(hasErrors: boolean): void {
80-
// No-op: secondary contexts do not control compilation state
132+
override get waitUntilReady(): Promise<boolean> {
133+
return this.primaryContext?.waitUntilReady ?? Promise.resolve(false);
81134
}
82135

83-
override markAsInProgress(): void {
84-
// No-op: secondary contexts do not control compilation state
136+
override getCompilerOptions(): Promise<CompilerOptions> {
137+
return this.primaryContext?.getCompilerOptions() ?? Promise.resolve({});
85138
}
86139

87140
override async dispose(): Promise<void> {

0 commit comments

Comments
 (0)