Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fixup! perf(@angular/build): cache transformed configuration in TypeS…
…cript compilation
  • Loading branch information
alan-agius4 committed Oct 1, 2026
commit 6c1e983ddf90a8d5f395ecf3a9d1a04c543e099e
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,41 @@ describe('AngularCompilation', () => {
expect(result.compilerOptions.target).toBe(ts.ScriptTarget.ES2022);
expect(result.compilerOptions.inlineSources).toBe(true);
expect(result.warnings.length).toBeGreaterThan(0);
expect(result.tsConfigFiles).toEqual(['tsconfig.json']);
});

it('caches transformed configuration when rootFiles are provided and invalidates on config change', async () => {
const compilation = new MockTypeScriptCompilation();
const mockReadConfig = jasmine.createSpy('readConfiguration').and.returnValue({
options: { target: ts.ScriptTarget.ES2020 },
rootNames: ['/src/main.ts'],
errors: [],
});
spyOn(TypeScriptCompilation, 'loadCompilerCli').and.resolveTo({
readConfiguration: mockReadConfig,
} as unknown as typeof import('@angular/compiler-cli'));

const overrides: CompilerOptionOverrides = { rootFiles: ['/src/main.ts'] };

const result1 = await compilation.testLoadConfiguration('tsconfig.json', overrides);
expect(mockReadConfig).toHaveBeenCalledTimes(1);

// Re-loading with same rootFiles should return cached configuration without calling readConfiguration
const result2 = await compilation.testLoadConfiguration('tsconfig.json', overrides);
expect(mockReadConfig).toHaveBeenCalledTimes(1);
expect(result2).toBe(result1);

// Invalidation of non-config file should keep cache intact
await compilation.update?.(new Set(['/src/main.ts']));
const result3 = await compilation.testLoadConfiguration('tsconfig.json', overrides);
expect(mockReadConfig).toHaveBeenCalledTimes(1);
expect(result3).toBe(result1);

// Invalidation of tsconfig file should clear cache
await compilation.update?.(new Set(['tsconfig.json']));
const result4 = await compilation.testLoadConfiguration('tsconfig.json', overrides);
expect(mockReadConfig).toHaveBeenCalledTimes(2);
expect(result4).toBeDefined();
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export class AotCompilation extends TypeScriptCompilation {
rootNames,
errors: configurationDiagnostics,
warnings,
extendedConfigFiles,
tsConfigFiles,
} = await this.loadConfiguration(tsconfig, compilerOptionOverrides, buildType);

const useTypeScriptTranspilation =
Expand Down Expand Up @@ -211,7 +211,7 @@ export class AotCompilation extends TypeScriptCompilation {

// Get all files referenced in the TypeScript/Angular program including component resources
const referencedFiles = [
...(extendedConfigFiles ?? []),
...tsConfigFiles,
...typeScriptProgram
.getSourceFiles()
.filter((sourceFile) => !angularCompiler.ignoreForEmit.has(sourceFile))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class JitCompilation extends TypeScriptCompilation {
rootNames,
errors: configurationDiagnostics,
warnings,
extendedConfigFiles,
tsConfigFiles,
} = await this.loadConfiguration(tsconfig, compilerOptionOverrides, buildType);

if (hostOptions.modifiedFiles) {
Expand Down Expand Up @@ -91,7 +91,7 @@ export class JitCompilation extends TypeScriptCompilation {
);

const referencedFiles = [
...(extendedConfigFiles ?? []),
...tsConfigFiles,
...typeScriptProgram.getSourceFiles().map((sourceFile) => sourceFile.fileName),
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ export interface TransformedConfiguration {
rootNames: string[];
errors: ts.Diagnostic[];
warnings: PartialMessage[];
extendedConfigFiles?: readonly string[];
tsConfigFiles: readonly string[];
}

export abstract class TypeScriptCompilation extends AngularCompilation {
static #angularCompilerCliModule?: typeof ng;
#cachedConfiguration?: TransformedConfiguration;
Comment thread
alan-agius4 marked this conversation as resolved.
#cachedRootFiles?: readonly string[];
#tsConfigFiles?: Set<string>;
readonly #extendedConfigCache = new Map<string, ts.ExtendedConfigCacheEntry>();

static async loadCompilerCli(): Promise<typeof ng> {
Expand Down Expand Up @@ -91,6 +92,8 @@ export abstract class TypeScriptCompilation extends AngularCompilation {
),
);

this.#tsConfigFiles = new Set([toPosixPath(tsconfig), ...this.#extendedConfigCache.keys()]);

let rootNames = originalRootNames;
if (compilerOptionOverrides?.rootFiles?.length) {
const rootFilesSet = new Set(
Expand All @@ -117,10 +120,10 @@ export abstract class TypeScriptCompilation extends AngularCompilation {
rootNames,
errors,
warnings,
extendedConfigFiles: [...this.#extendedConfigCache.keys()],
tsConfigFiles: Array.from(this.#tsConfigFiles),
};

if (config.extendedConfigFiles?.length && currentRootFiles?.length) {
if (currentRootFiles?.length) {
this.#cachedConfiguration = config;
this.#cachedRootFiles = currentRootFiles;
}
Expand All @@ -131,34 +134,36 @@ export abstract class TypeScriptCompilation extends AngularCompilation {
protected readonly sourceFiles = new Map<string, ts.SourceFile>();

protected invalidateFiles(files: Iterable<string>): void {
const tsConfigFiles = this.#tsConfigFiles;

for (const file of files) {
const posixFile = toPosixPath(file);
this.sourceFiles.delete(posixFile);

if (!this.#extendedConfigCache.size) {
if (!tsConfigFiles) {
continue;
}

let cacheKey: string | undefined;
if (this.#extendedConfigCache.has(posixFile)) {
cacheKey = posixFile;
let tsConfigCachedPath: string | undefined;
if (tsConfigFiles.has(posixFile)) {
tsConfigCachedPath = posixFile;
} else {
// Check with lowercased key because TypeScript lowercases the keys
// of the extended config cache on case-insensitive operating systems.
const lowerCasedPosixFile = posixFile.toLowerCase();
if (this.#extendedConfigCache.has(lowerCasedPosixFile)) {
cacheKey = lowerCasedPosixFile;
if (tsConfigFiles.has(lowerCasedPosixFile)) {
tsConfigCachedPath = lowerCasedPosixFile;
}
}

if (!cacheKey) {
if (!tsConfigCachedPath) {
continue;
}

// If a tsconfig changes, we need to re-read the configuration.
this.#cachedConfiguration = undefined;
this.#cachedRootFiles = undefined;
this.#extendedConfigCache.delete(cacheKey);
this.#extendedConfigCache.delete(tsConfigCachedPath);
}
}

Expand Down