From 75998ebabb041f60aab40bf5a11979e8f3615537 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:12:38 -0500 Subject: [PATCH] perf(@angular/build): reuse TS package.json cache when rebuilding 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. --- .../build/src/tools/angular/angular-host.ts | 18 ++++++------ .../angular/compilation/aot-compilation.ts | 28 ++++++++++++++----- .../angular/compilation/jit-compilation.ts | 2 +- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/packages/angular/build/src/tools/angular/angular-host.ts b/packages/angular/build/src/tools/angular/angular-host.ts index 1750002f1cdd..103b3e37ac68 100644 --- a/packages/angular/build/src/tools/angular/angular-host.ts +++ b/packages/angular/build/src/tools/angular/angular-host.ts @@ -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); @@ -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); } diff --git a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts index 19e43251e950..f111224c36d8 100644 --- a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts @@ -74,25 +74,39 @@ export class AotCompilation extends AngularCompilation { hostOptions.externalStylesheets ??= new Map(); } + // Reuse the package.json cache from the previous compilation + const packageJsonCache = this.#state?.compilerHost + .getModuleResolutionCache?.() + ?.getPackageJsonInfoCache(); + const useHmr = compilerOptions['_enableHmr'] && hostOptions.modifiedFiles && hostOptions.modifiedFiles.size <= HMR_MODIFIED_FILE_LIMIT; - // Collect stale source files for HMR analysis of inline component resources let staleSourceFiles; - if (useHmr && 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(); - 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(); + 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( diff --git a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts index eab21a8608c5..db2de81b4ae7 100644 --- a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts @@ -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', () =>