Skip to content

Commit 01fb532

Browse files
committed
fixup! perf(@angular/build): cache transformed configuration in TypeScript compilation
1 parent 7079a2d commit 01fb532

4 files changed

Lines changed: 139 additions & 37 deletions

File tree

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,88 @@ describe('AngularCompilation', () => {
158158
suppressOutputPathCheck: true,
159159
outDir: undefined,
160160
}),
161+
undefined,
162+
jasmine.any(Map),
161163
);
162164
expect(result.rootNames).toEqual(['/src/main.ts']);
163165
expect(result.compilerOptions.target).toBe(ts.ScriptTarget.ES2022);
164166
expect(result.compilerOptions.inlineSources).toBe(true);
165167
expect(result.warnings.length).toBeGreaterThan(0);
166168
});
169+
170+
it('passes extendedConfigCache to readConfiguration and returns its keys in extendedConfigFiles', async () => {
171+
const compilation = new MockTypeScriptCompilation();
172+
const mockReadConfig = jasmine
173+
.createSpy('readConfiguration')
174+
.and.callFake(
175+
(
176+
_project: string,
177+
_options?: unknown,
178+
_host?: unknown,
179+
extendedConfigCache?: Map<string, ts.ExtendedConfigCacheEntry>,
180+
) => {
181+
extendedConfigCache?.set(
182+
'/workspace/tsconfig.base.json',
183+
{} as ts.ExtendedConfigCacheEntry,
184+
);
185+
186+
return {
187+
options: { target: ts.ScriptTarget.ES2022 },
188+
rootNames: ['/src/main.ts'],
189+
errors: [],
190+
};
191+
},
192+
);
193+
spyOn(TypeScriptCompilation, 'loadCompilerCli').and.resolveTo({
194+
readConfiguration: mockReadConfig,
195+
} as unknown as typeof import('@angular/compiler-cli'));
196+
197+
const result = await compilation.testLoadConfiguration('tsconfig.json');
198+
199+
expect(mockReadConfig).toHaveBeenCalledWith(
200+
'tsconfig.json',
201+
jasmine.anything(),
202+
undefined,
203+
jasmine.any(Map),
204+
);
205+
expect(result.extendedConfigFiles).toEqual(['/workspace/tsconfig.base.json']);
206+
});
207+
208+
it('clears extendedConfigCache when a tsconfig file is invalidated', async () => {
209+
const compilation = new MockTypeScriptCompilation();
210+
let capturedCache: Map<string, ts.ExtendedConfigCacheEntry> | undefined;
211+
const mockReadConfig = jasmine
212+
.createSpy('readConfiguration')
213+
.and.callFake(
214+
(
215+
_project: string,
216+
_options?: unknown,
217+
_host?: unknown,
218+
extendedConfigCache?: Map<string, ts.ExtendedConfigCacheEntry>,
219+
) => {
220+
capturedCache = extendedConfigCache;
221+
extendedConfigCache?.set(
222+
'/workspace/tsconfig.base.json',
223+
{} as ts.ExtendedConfigCacheEntry,
224+
);
225+
226+
return {
227+
options: { target: ts.ScriptTarget.ES2022 },
228+
rootNames: ['/src/main.ts'],
229+
errors: [],
230+
};
231+
},
232+
);
233+
spyOn(TypeScriptCompilation, 'loadCompilerCli').and.resolveTo({
234+
readConfiguration: mockReadConfig,
235+
} as unknown as typeof import('@angular/compiler-cli'));
236+
237+
await compilation.testLoadConfiguration('tsconfig.json');
238+
expect(capturedCache?.has('/workspace/tsconfig.base.json')).toBeTrue();
239+
240+
await compilation.update(new Set(['/workspace/tsconfig.base.json']));
241+
expect(capturedCache?.has('/workspace/tsconfig.base.json')).toBeFalse();
242+
});
167243
});
168244

169245
describe('createAngularCompilation', () => {

‎packages/angular/build/src/tools/angular/compilation/aot-compilation.ts‎

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export class AotCompilation extends TypeScriptCompilation {
7777
rootNames,
7878
errors: configurationDiagnostics,
7979
warnings,
80+
extendedConfigFiles,
8081
} = await this.loadConfiguration(tsconfig, compilerOptionOverrides, buildType);
8182

8283
const useTypeScriptTranspilation =
@@ -209,25 +210,28 @@ export class AotCompilation extends TypeScriptCompilation {
209210
const componentResourcesDependencies = new Map<string, string[]>();
210211

211212
// Get all files referenced in the TypeScript/Angular program including component resources
212-
const referencedFiles = typeScriptProgram
213-
.getSourceFiles()
214-
.filter((sourceFile) => !angularCompiler.ignoreForEmit.has(sourceFile))
215-
.flatMap((sourceFile) => {
216-
const resourceDependencies = angularCompiler.getResourceDependencies(sourceFile);
217-
componentResourcesDependencies.set(sourceFile.fileName, resourceDependencies);
218-
// Also invalidate Angular diagnostics for a source file if component resources are modified
219-
if (this.#state && hostOptions.modifiedFiles?.size) {
220-
for (const resourceDependency of resourceDependencies) {
221-
if (hostOptions.modifiedFiles.has(resourceDependency)) {
222-
this.#state.diagnosticCache.delete(sourceFile);
223-
// Also mark as affected in case changed template affects diagnostics
224-
affectedFiles.add(sourceFile);
213+
const referencedFiles = [
214+
...(extendedConfigFiles ?? []),
215+
...typeScriptProgram
216+
.getSourceFiles()
217+
.filter((sourceFile) => !angularCompiler.ignoreForEmit.has(sourceFile))
218+
.flatMap((sourceFile) => {
219+
const resourceDependencies = angularCompiler.getResourceDependencies(sourceFile);
220+
componentResourcesDependencies.set(sourceFile.fileName, resourceDependencies);
221+
// Also invalidate Angular diagnostics for a source file if component resources are modified
222+
if (this.#state && hostOptions.modifiedFiles?.size) {
223+
for (const resourceDependency of resourceDependencies) {
224+
if (hostOptions.modifiedFiles.has(resourceDependency)) {
225+
this.#state.diagnosticCache.delete(sourceFile);
226+
// Also mark as affected in case changed template affects diagnostics
227+
affectedFiles.add(sourceFile);
228+
}
225229
}
226230
}
227-
}
228231

229-
return [sourceFile.fileName, ...resourceDependencies];
230-
});
232+
return [sourceFile.fileName, ...resourceDependencies];
233+
}),
234+
];
231235

232236
this.#state = new AngularCompilationState(
233237
angularProgram,

‎packages/angular/build/src/tools/angular/compilation/jit-compilation.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export class JitCompilation extends TypeScriptCompilation {
5555
rootNames,
5656
errors: configurationDiagnostics,
5757
warnings,
58+
extendedConfigFiles,
5859
} = await this.loadConfiguration(tsconfig, compilerOptionOverrides, buildType);
5960

6061
if (hostOptions.modifiedFiles) {
@@ -89,9 +90,10 @@ export class JitCompilation extends TypeScriptCompilation {
8990
createWorkerTransformer(hostOptions.processWebWorker.bind(hostOptions)),
9091
);
9192

92-
const referencedFiles = typeScriptProgram
93-
.getSourceFiles()
94-
.map((sourceFile) => sourceFile.fileName);
93+
const referencedFiles = [
94+
...(extendedConfigFiles ?? []),
95+
...typeScriptProgram.getSourceFiles().map((sourceFile) => sourceFile.fileName),
96+
];
9597

9698
return { compilerOptions, referencedFiles, warnings };
9799
}

‎packages/angular/build/src/tools/angular/compilation/typescript-compilation.ts‎

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ export interface TransformedConfiguration {
2020
rootNames: string[];
2121
errors: ts.Diagnostic[];
2222
warnings: PartialMessage[];
23+
extendedConfigFiles?: readonly string[];
2324
}
2425

2526
export abstract class TypeScriptCompilation extends AngularCompilation {
2627
static #angularCompilerCliModule?: typeof ng;
2728
#cachedConfiguration?: TransformedConfiguration;
2829
#cachedRootFiles?: readonly string[];
30+
readonly #extendedConfigCache = new Map<string, ts.ExtendedConfigCacheEntry>();
2931

3032
static async loadCompilerCli(): Promise<typeof ng> {
3133
TypeScriptCompilation.#angularCompilerCliModule ??= await import('@angular/compiler-cli');
@@ -51,29 +53,41 @@ export abstract class TypeScriptCompilation extends AngularCompilation {
5153
return this.#cachedConfiguration;
5254
}
5355

54-
const { readConfiguration } = await TypeScriptCompilation.loadCompilerCli();
56+
const { readConfiguration } = (await TypeScriptCompilation.loadCompilerCli()) as typeof ng & {
57+
readConfiguration(
58+
project: string,
59+
existingOptions?: ng.CompilerOptions,
60+
host?: unknown,
61+
extendedConfigCache?: Map<string, ts.ExtendedConfigCacheEntry>,
62+
): ng.ParsedConfiguration;
63+
};
5564

5665
const {
5766
options: originalCompilerOptions,
5867
rootNames: originalRootNames,
5968
errors,
6069
} = profileSync('NG_READ_CONFIG', () =>
61-
readConfiguration(tsconfig, {
62-
// Angular specific configuration defaults and overrides to ensure a functioning compilation.
63-
suppressOutputPathCheck: true,
64-
outDir: undefined,
65-
sourceMap: false,
66-
declaration: false,
67-
declarationMap: false,
68-
allowEmptyCodegenFiles: false,
69-
annotationsAs: 'decorators',
70-
enableResourceInlining: false,
71-
supportTestBed: false,
72-
supportJitMode: false,
73-
// Disable removing of comments as TS is quite aggressive with these and can
74-
// remove important annotations, such as /* @__PURE__ */ and comments like /* vite-ignore */.
75-
removeComments: false,
76-
}),
70+
readConfiguration(
71+
tsconfig,
72+
{
73+
// Angular specific configuration defaults and overrides to ensure a functioning compilation.
74+
suppressOutputPathCheck: true,
75+
outDir: undefined,
76+
sourceMap: false,
77+
declaration: false,
78+
declarationMap: false,
79+
allowEmptyCodegenFiles: false,
80+
annotationsAs: 'decorators',
81+
enableResourceInlining: false,
82+
supportTestBed: false,
83+
supportJitMode: false,
84+
// Disable removing of comments as TS is quite aggressive with these and can
85+
// remove important annotations, such as /* @__PURE__ */ and comments like /* vite-ignore */.
86+
removeComments: false,
87+
},
88+
undefined,
89+
this.#extendedConfigCache,
90+
),
7791
);
7892

7993
let rootNames = originalRootNames;
@@ -102,6 +116,7 @@ export abstract class TypeScriptCompilation extends AngularCompilation {
102116
rootNames,
103117
errors,
104118
warnings,
119+
extendedConfigFiles: [...this.#extendedConfigCache.keys()],
105120
};
106121

107122
if (currentRootFiles?.length) {
@@ -118,10 +133,15 @@ export abstract class TypeScriptCompilation extends AngularCompilation {
118133
for (const file of files) {
119134
const posixFile = toPosixPath(file);
120135
this.sourceFiles.delete(posixFile);
121-
if (/\.json$/i.test(posixFile)) {
136+
if (
137+
/\.json$/i.test(posixFile) ||
138+
this.#extendedConfigCache.has(posixFile) ||
139+
this.#extendedConfigCache.has(canonicalizePath(posixFile))
140+
) {
122141
// If a tsconfig changes, we need to re-read the configuration.
123142
this.#cachedConfiguration = undefined;
124143
this.#cachedRootFiles = undefined;
144+
this.#extendedConfigCache.clear();
125145
}
126146
}
127147
}

0 commit comments

Comments
 (0)