Skip to content

Commit

Permalink
perf(@angular/build): reuse TS package.json cache when rebuilding
Browse files Browse the repository at this point in the history
TypeScript 5.6 and higher added functionality that will search for a
`package.json` file for source files that are part of the program (e.g., `.d.ts`)
and within a node modules directory. This can be an expensive tasks especially
considering the large amount of `.d.ts` files within packages. TypeScript supports
using a cache of known `package.json` files to improve the performance of this task.
The Angular CLI will now provide and reuse this cache across rebuilds during watch
mode. This includes the use of `ng serve`.

The performance difference is most apparent for the Angular template diagnostic
step of the build. Internally the Angular compiler creates a new template typechecking
program which causes the `package.json` search process to occur. By leveraging the
cache, this process becomes a series of cache hits. In the event that files are modified
within the node modules directory, the cache is invalidated and the following rebuild
may be longer as a result.

(cherry picked from commit 75998eb)
  • Loading branch information
clydin authored and alan-agius4 committed Dec 5, 2024
1 parent 23667ed commit 14451e2
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 16 deletions.
18 changes: 10 additions & 8 deletions packages/angular/build/src/tools/angular/angular-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export function createAngularCompilerHost(
typescript: typeof ts,
compilerOptions: AngularCompilerOptions,
hostOptions: AngularHostOptions,
packageJsonCache: ts.PackageJsonInfoCache | undefined,
): AngularCompilerHost {
// Create TypeScript compiler host
const host: AngularCompilerHost = typescript.createIncrementalCompilerHost(compilerOptions);
Expand Down Expand Up @@ -229,16 +230,17 @@ export function createAngularCompilerHost(
return hostOptions.modifiedFiles;
};

// Provide a resolution cache to ensure package.json lookups are cached
const resolutionCache = typescript.createModuleResolutionCache(
host.getCurrentDirectory(),
host.getCanonicalFileName.bind(host),
compilerOptions,
packageJsonCache,
);
host.getModuleResolutionCache = () => resolutionCache;

// Augment TypeScript Host for file replacements option
if (hostOptions.fileReplacements) {
// Provide a resolution cache since overriding resolution prevents automatic creation
const resolutionCache = typescript.createModuleResolutionCache(
host.getCurrentDirectory(),
host.getCanonicalFileName.bind(host),
compilerOptions,
);
host.getModuleResolutionCache = () => resolutionCache;

augmentHostWithReplacements(typescript, host, hostOptions.fileReplacements, resolutionCache);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,36 @@ export class AotCompilation extends AngularCompilation {
hostOptions.externalStylesheets ??= new Map();
}

// Collect stale source files for HMR analysis of inline component resources
// Reuse the package.json cache from the previous compilation
const packageJsonCache = this.#state?.compilerHost
.getModuleResolutionCache?.()
?.getPackageJsonInfoCache();

const useHmr = compilerOptions['_enableHmr'];

let staleSourceFiles;
if (compilerOptions['_enableHmr'] && hostOptions.modifiedFiles && this.#state) {
let clearPackageJsonCache = false;
if (hostOptions.modifiedFiles && this.#state) {
for (const modifiedFile of hostOptions.modifiedFiles) {
const sourceFile = this.#state.typeScriptProgram.getSourceFile(modifiedFile);
if (sourceFile) {
staleSourceFiles ??= new Map<string, ts.SourceFile>();
staleSourceFiles.set(modifiedFile, sourceFile);
// Clear package.json cache if a node modules file was modified
if (!clearPackageJsonCache && modifiedFile.includes('node_modules')) {
clearPackageJsonCache = true;
packageJsonCache?.clear();
}

// Collect stale source files for HMR analysis of inline component resources
if (useHmr) {
const sourceFile = this.#state.typeScriptProgram.getSourceFile(modifiedFile);
if (sourceFile) {
staleSourceFiles ??= new Map<string, ts.SourceFile>();
staleSourceFiles.set(modifiedFile, sourceFile);
}
}
}
}

// Create Angular compiler host
const host = createAngularCompilerHost(ts, compilerOptions, hostOptions);
const host = createAngularCompilerHost(ts, compilerOptions, hostOptions, packageJsonCache);

// Create the Angular specific program that contains the Angular compiler
const angularProgram = profileSync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class JitCompilation extends AngularCompilation {
compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions;

// Create Angular compiler host
const host = createAngularCompilerHost(ts, compilerOptions, hostOptions);
const host = createAngularCompilerHost(ts, compilerOptions, hostOptions, undefined);

// Create the TypeScript Program
const typeScriptProgram = profileSync('TS_CREATE_PROGRAM', () =>
Expand Down

0 comments on commit 14451e2

Please sign in to comment.