diff --git a/packages/core/package.json b/packages/core/package.json index 6f56f7a..2683f9d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -53,6 +53,7 @@ "@andrewbranch/untar.js": "^1.0.3", "cjs-module-lexer": "^1.2.3", "fflate": "^0.8.2", + "lru-cache": "^11.0.1", "semver": "^7.5.4", "typescript": "5.6.1-rc", "validate-npm-package-name": "^5.0.0" diff --git a/packages/core/src/internal/multiCompilerHost.ts b/packages/core/src/internal/multiCompilerHost.ts index ff77c03..9fb3717 100644 --- a/packages/core/src/internal/multiCompilerHost.ts +++ b/packages/core/src/internal/multiCompilerHost.ts @@ -1,4 +1,5 @@ import ts from "typescript"; +import { LRUCache } from "lru-cache"; import type { ModuleKind } from "../types.js"; import type { Package } from "../createPackage.js"; @@ -37,6 +38,7 @@ const getCanonicalFileName = ts.createGetCanonicalFileName(false); const toPath = (fileName: string) => ts.toPath(fileName, "/", getCanonicalFileName); export class CompilerHostWrapper { + private programCache = new LRUCache({ max: 2 }); private compilerHost: ts.CompilerHost; private compilerOptions: ts.CompilerOptions; private normalModuleResolutionCache: ts.ModuleResolutionCache; @@ -162,12 +164,17 @@ export class CompilerHostWrapper { return `${resolutionMode ?? 1}:${+!!noDtsResolution}:${+!!allowJs}:${moduleSpecifier}`; } + private getProgram(rootNames: readonly string[], options: ts.CompilerOptions) { + const key = programKey(rootNames, options); + let program = this.programCache.get(key); + if (!program) { + this.programCache.set(key, (program = ts.createProgram({ rootNames, options, host: this.compilerHost }))); + } + return program; + } + createPrimaryProgram(rootName: string) { - const program = ts.createProgram({ - rootNames: [rootName], - options: this.compilerOptions, - host: this.compilerHost, - }); + const program = this.getProgram([rootName], this.compilerOptions); program.resolvedModules?.forEach((cache, path) => { let ownCache = this.resolvedModules.get(path); @@ -199,11 +206,8 @@ export class CompilerHostWrapper { ) { throw new Error("Cannot override resolution-affecting options for host due to potential cache polution"); } - return ts.createProgram({ - rootNames, - options: extraOptions ? { ...this.compilerOptions, ...extraOptions } : this.compilerOptions, - host: this.compilerHost, - }); + const options = extraOptions ? { ...this.compilerOptions, ...extraOptions } : this.compilerOptions; + return this.getProgram(rootNames, options); } getResolvedModule(sourceFile: ts.SourceFile, moduleName: string, resolutionMode: ts.ResolutionMode) { @@ -304,3 +308,7 @@ class TraceCollector { this.traces.length = 0; } } + +function programKey(rootNames: readonly string[], options: ts.CompilerOptions) { + return JSON.stringify([rootNames, Object.entries(options).sort(([k1], [k2]) => k1.localeCompare(k2))]); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61e6946..2350a9c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: fflate: specifier: ^0.8.2 version: 0.8.2 + lru-cache: + specifier: ^11.0.1 + version: 11.0.1 semver: specifier: ^7.5.4 version: 7.5.4 @@ -1434,6 +1437,10 @@ packages: resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} engines: {node: 14 || >=16.14} + lru-cache@11.0.1: + resolution: {integrity: sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==} + engines: {node: 20 || >=22} + lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -3686,6 +3693,8 @@ snapshots: lru-cache@10.0.1: {} + lru-cache@11.0.1: {} + lru-cache@4.1.5: dependencies: pseudomap: 1.0.2