Skip to content

Commit 960403a

Browse files
committed
Cache package.json lookup results from module resolution
1 parent 9e4cd0b commit 960403a

File tree

7 files changed

+118
-38
lines changed

7 files changed

+118
-38
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5041,6 +5041,14 @@
50415041
"code": 6387,
50425042
"reportsDeprecated": true
50435043
},
5044+
"Using cached result of 'package.json' at '{0}' that indicates it was found.": {
5045+
"category": "Message",
5046+
"code": 6388
5047+
},
5048+
"Using cached result of 'package.json' at '{0}' that indicates it was not found.": {
5049+
"category": "Message",
5050+
"code": 6389
5051+
},
50445052

50455053
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
50465054
"category": "Message",

src/compiler/moduleNameResolver.ts

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,11 @@ namespace ts {
101101
traceEnabled: boolean;
102102
failedLookupLocations: Push<string>;
103103
resultFromCache?: ResolvedModuleWithFailedLookupLocations;
104+
packageJsonInfoCache: PackageJsonInfoCache | undefined;
104105
}
105106

106107
/** Just the fields that we use for module resolution. */
108+
/*@internal*/
107109
interface PackageJsonPathFields {
108110
typings?: string;
109111
types?: string;
@@ -179,6 +181,7 @@ namespace ts {
179181
return typesVersions;
180182
}
181183

184+
/*@internal*/
182185
interface VersionPaths {
183186
version: string;
184187
paths: MapLike<string[]>;
@@ -281,13 +284,13 @@ namespace ts {
281284
* This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups
282285
* is assumed to be the same as root directory of the project.
283286
*/
284-
export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
287+
export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, packageJsonInfoCache?: PackageJsonInfoCache): ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
285288
const traceEnabled = isTraceEnabled(options, host);
286289
if (redirectedReference) {
287290
options = redirectedReference.commandLine.options;
288291
}
289292
const failedLookupLocations: string[] = [];
290-
const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations };
293+
const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations, packageJsonInfoCache };
291294

292295
const typeRoots = getEffectiveTypeRoots(options, host);
293296
if (traceEnabled) {
@@ -441,7 +444,7 @@ namespace ts {
441444
* Cached module resolutions per containing directory.
442445
* This assumes that any module id will have the same resolution for sibling files located in the same folder.
443446
*/
444-
export interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache {
447+
export interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache, PackageJsonInfoCache {
445448
getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map<ResolvedModuleWithFailedLookupLocations>;
446449
clear(): void;
447450
/**
@@ -455,10 +458,16 @@ namespace ts {
455458
* Stored map from non-relative module name to a table: directory -> result of module lookup in this directory
456459
* We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive.
457460
*/
458-
export interface NonRelativeModuleNameResolutionCache {
461+
export interface NonRelativeModuleNameResolutionCache extends PackageJsonInfoCache {
459462
getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache;
460463
}
461464

465+
export interface PackageJsonInfoCache {
466+
/*@internal*/ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfo | false | undefined;
467+
/*@internal*/ setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | false): void;
468+
clear(): void;
469+
}
470+
462471
export interface PerModuleNameCache {
463472
get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined;
464473
set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void;
@@ -525,14 +534,29 @@ namespace ts {
525534
}
526535
}
527536

537+
export function createPackageJsonInfoCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): PackageJsonInfoCache {
538+
let cache: ESMap<Path, PackageJsonInfo | false> | undefined;
539+
return { getPackageJsonInfo, setPackageJsonInfo, clear };
540+
function getPackageJsonInfo(packageJsonPath: string) {
541+
return cache?.get(toPath(packageJsonPath, currentDirectory, getCanonicalFileName));
542+
}
543+
function setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | false) {
544+
(cache ||= new Map()).set(toPath(packageJsonPath, currentDirectory, getCanonicalFileName), info);
545+
}
546+
function clear() {
547+
cache = undefined;
548+
}
549+
}
550+
528551
/*@internal*/
529552
export function createModuleResolutionCacheWithMaps(
530553
directoryToModuleNameMap: CacheWithRedirects<ESMap<string, ResolvedModuleWithFailedLookupLocations>>,
531554
moduleNameToDirectoryMap: CacheWithRedirects<PerModuleNameCache>,
532555
currentDirectory: string,
533556
getCanonicalFileName: GetCanonicalFileName): ModuleResolutionCache {
534-
557+
const packageJsonInfoCache = createPackageJsonInfoCache(currentDirectory, getCanonicalFileName);
535558
return {
559+
...packageJsonInfoCache,
536560
getOrCreateCacheForDirectory,
537561
getOrCreateCacheForModuleName,
538562
clear,
@@ -542,6 +566,7 @@ namespace ts {
542566
function clear() {
543567
directoryToModuleNameMap.clear();
544568
moduleNameToDirectoryMap.clear();
569+
packageJsonInfoCache.clear();
545570
}
546571

547572
function update(options: CompilerOptions) {
@@ -969,7 +994,7 @@ namespace ts {
969994
const traceEnabled = isTraceEnabled(compilerOptions, host);
970995

971996
const failedLookupLocations: string[] = [];
972-
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
997+
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache };
973998

974999
const result = forEach(extensions, ext => tryResolve(ext));
9751000
return createResolvedModuleWithFailedLookupLocations(result?.value?.resolved, result?.value?.isExternalLibraryImport, failedLookupLocations, state.resultFromCache);
@@ -1175,6 +1200,7 @@ namespace ts {
11751200
return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths));
11761201
}
11771202

1203+
/*@internal*/
11781204
interface PackageJsonInfo {
11791205
packageDirectory: string;
11801206
packageJsonContent: PackageJsonPathFields;
@@ -1183,21 +1209,40 @@ namespace ts {
11831209

11841210
function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined {
11851211
const { host, traceEnabled } = state;
1186-
const directoryExists = !onlyRecordFailures && directoryProbablyExists(packageDirectory, host);
11871212
const packageJsonPath = combinePaths(packageDirectory, "package.json");
1213+
if (onlyRecordFailures) {
1214+
state.failedLookupLocations.push(packageJsonPath);
1215+
return undefined;
1216+
}
1217+
1218+
const existing = state.packageJsonInfoCache?.getPackageJsonInfo(packageJsonPath);
1219+
if (existing !== undefined) {
1220+
if (existing) {
1221+
if (traceEnabled) trace(host, Diagnostics.Using_cached_result_of_package_json_at_0_that_indicates_it_was_found, packageJsonPath);
1222+
return existing;
1223+
}
1224+
else {
1225+
if (traceEnabled) trace(host, Diagnostics.Using_cached_result_of_package_json_at_0_that_indicates_it_was_not_found, packageJsonPath);
1226+
state.failedLookupLocations.push(packageJsonPath);
1227+
return undefined;
1228+
}
1229+
}
1230+
const directoryExists = directoryProbablyExists(packageDirectory, host);
11881231
if (directoryExists && host.fileExists(packageJsonPath)) {
11891232
const packageJsonContent = readJson(packageJsonPath, host) as PackageJson;
11901233
if (traceEnabled) {
11911234
trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath);
11921235
}
11931236
const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state);
1194-
return { packageDirectory, packageJsonContent, versionPaths };
1237+
const result = { packageDirectory, packageJsonContent, versionPaths };
1238+
state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, result);
1239+
return result;
11951240
}
11961241
else {
11971242
if (directoryExists && traceEnabled) {
11981243
trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath);
11991244
}
1200-
1245+
state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, false);
12011246
// record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results
12021247
state.failedLookupLocations.push(packageJsonPath);
12031248
}
@@ -1486,7 +1531,7 @@ namespace ts {
14861531
export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations {
14871532
const traceEnabled = isTraceEnabled(compilerOptions, host);
14881533
const failedLookupLocations: string[] = [];
1489-
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
1534+
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache };
14901535
const containingDirectory = getDirectoryPath(containingFile);
14911536

14921537
const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript);
@@ -1530,13 +1575,13 @@ namespace ts {
15301575
* This is the minumum code needed to expose that functionality; the rest is in the host.
15311576
*/
15321577
/* @internal */
1533-
export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string): ResolvedModuleWithFailedLookupLocations {
1578+
export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string, packageJsonInfoCache: PackageJsonInfoCache): ResolvedModuleWithFailedLookupLocations {
15341579
const traceEnabled = isTraceEnabled(compilerOptions, host);
15351580
if (traceEnabled) {
15361581
trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache);
15371582
}
15381583
const failedLookupLocations: string[] = [];
1539-
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
1584+
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache };
15401585
const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false);
15411586
return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations, state.resultFromCache);
15421587
}

src/compiler/program.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,7 @@ namespace ts {
843843
let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression | false | undefined;
844844

845845
let moduleResolutionCache: ModuleResolutionCache | undefined;
846+
let packageJsonInfoCache: PackageJsonInfoCache | undefined;
846847
let actualResolveModuleNamesWorker: (moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[];
847848
const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
848849
if (host.resolveModuleNames) {
@@ -857,7 +858,7 @@ namespace ts {
857858
});
858859
}
859860
else {
860-
moduleResolutionCache = createModuleResolutionCache(currentDirectory, x => host.getCanonicalFileName(x), options);
861+
moduleResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options);
861862
const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, options, host, moduleResolutionCache, redirectedReference).resolvedModule!; // TODO: GH#18217
862863
actualResolveModuleNamesWorker = (moduleNames, containingFile, _reusedNames, redirectedReference) => loadWithLocalCache<ResolvedModuleFull>(Debug.checkEachDefined(moduleNames), containingFile, redirectedReference, loader);
863864
}
@@ -867,7 +868,15 @@ namespace ts {
867868
actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference) => host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options);
868869
}
869870
else {
870-
const loader = (typesRef: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveTypeReferenceDirective(typesRef, containingFile, options, host, redirectedReference).resolvedTypeReferenceDirective!; // TODO: GH#18217
871+
packageJsonInfoCache = moduleResolutionCache || createPackageJsonInfoCache(currentDirectory, getCanonicalFileName);
872+
const loader = (typesRef: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveTypeReferenceDirective(
873+
typesRef,
874+
containingFile,
875+
options,
876+
host,
877+
redirectedReference,
878+
packageJsonInfoCache,
879+
).resolvedTypeReferenceDirective!; // TODO: GH#18217
871880
actualResolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile, redirectedReference) => loadWithLocalCache<ResolvedTypeReferenceDirective>(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, loader);
872881
}
873882

@@ -1036,6 +1045,8 @@ namespace ts {
10361045
);
10371046
}
10381047

1048+
packageJsonInfoCache = undefined;
1049+
10391050
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
10401051
oldProgram = undefined;
10411052

src/compiler/resolutionCache.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,9 @@ namespace ts {
319319
resolutionHost.projectName,
320320
compilerOptions,
321321
host,
322-
globalCache);
322+
globalCache,
323+
moduleResolutionCache,
324+
);
323325
if (resolvedModule) {
324326
// Modify existing resolution so its saved in the directory cache as well
325327
(primaryResult.resolvedModule as any) = resolvedModule;
@@ -332,6 +334,10 @@ namespace ts {
332334
return primaryResult;
333335
}
334336

337+
function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations {
338+
return ts.resolveTypeReferenceDirective(typeReferenceDirectiveName, containingFile, options, host, redirectedReference, moduleResolutionCache);
339+
}
340+
335341
interface ResolveNamesWithLocalCacheInput<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName> {
336342
names: readonly string[];
337343
containingFile: string;

src/testRunner/unittests/reuseProgramStructure.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ namespace ts {
474474
"File 'node_modules/@types/a.d.ts' does not exist.",
475475
"File 'node_modules/@types/a/index.d.ts' does not exist.",
476476
"Loading module 'a' from 'node_modules' folder, target file type 'JavaScript'.",
477-
"File 'node_modules/a/package.json' does not exist.",
477+
"Using cached result of 'package.json' at 'node_modules/a/package.json' that indicates it was not found.",
478478
"File 'node_modules/a.js' does not exist.",
479479
"File 'node_modules/a.jsx' does not exist.",
480480
"File 'node_modules/a/index.js' does not exist.",

0 commit comments

Comments
 (0)