1import { 2 Debug, ESMap, FileWatcher, Map, ModulePath, ModuleSpecifierCache, ModuleSpecifierOptions, nodeModulesPathPart, Path, 3 ResolvedModuleSpecifierInfo, UserPreferences, 4} from "./_namespaces/ts"; 5 6/** @internal */ 7export interface ModuleSpecifierResolutionCacheHost { 8 watchNodeModulesForPackageJsonChanges(directoryPath: string): FileWatcher; 9} 10 11/** @internal */ 12export function createModuleSpecifierCache(host: ModuleSpecifierResolutionCacheHost): ModuleSpecifierCache { 13 let containedNodeModulesWatchers: ESMap<string, FileWatcher> | undefined; 14 let cache: ESMap<Path, ResolvedModuleSpecifierInfo> | undefined; 15 let currentKey: string | undefined; 16 const result: ModuleSpecifierCache = { 17 get(fromFileName, toFileName, preferences, options) { 18 if (!cache || currentKey !== key(fromFileName, preferences, options)) return undefined; 19 return cache.get(toFileName); 20 }, 21 set(fromFileName, toFileName, preferences, options, modulePaths, moduleSpecifiers) { 22 ensureCache(fromFileName, preferences, options).set(toFileName, createInfo(modulePaths, moduleSpecifiers, /*isBlockedByPackageJsonDependencies*/ false)); 23 24 // If any module specifiers were generated based off paths in node_modules, 25 // a package.json file in that package was read and is an input to the cached. 26 // Instead of watching each individual package.json file, set up a wildcard 27 // directory watcher for any node_modules referenced and clear the cache when 28 // it sees any changes. 29 if (moduleSpecifiers) { 30 for (const p of modulePaths) { 31 if (p.isInNodeModules) { 32 // No trailing slash 33 const nodeModulesPath = p.path.substring(0, p.path.indexOf(nodeModulesPathPart) + nodeModulesPathPart.length - 1); 34 if (!containedNodeModulesWatchers?.has(nodeModulesPath)) { 35 (containedNodeModulesWatchers ||= new Map()).set( 36 nodeModulesPath, 37 host.watchNodeModulesForPackageJsonChanges(nodeModulesPath), 38 ); 39 } 40 } 41 } 42 } 43 }, 44 setModulePaths(fromFileName, toFileName, preferences, options, modulePaths) { 45 const cache = ensureCache(fromFileName, preferences, options); 46 const info = cache.get(toFileName); 47 if (info) { 48 info.modulePaths = modulePaths; 49 } 50 else { 51 cache.set(toFileName, createInfo(modulePaths, /*moduleSpecifiers*/ undefined, /*isBlockedByPackageJsonDependencies*/ undefined)); 52 } 53 }, 54 setBlockedByPackageJsonDependencies(fromFileName, toFileName, preferences, options, isBlockedByPackageJsonDependencies) { 55 const cache = ensureCache(fromFileName, preferences, options); 56 const info = cache.get(toFileName); 57 if (info) { 58 info.isBlockedByPackageJsonDependencies = isBlockedByPackageJsonDependencies; 59 } 60 else { 61 cache.set(toFileName, createInfo(/*modulePaths*/ undefined, /*moduleSpecifiers*/ undefined, isBlockedByPackageJsonDependencies)); 62 } 63 }, 64 clear() { 65 containedNodeModulesWatchers?.forEach(watcher => watcher.close()); 66 cache?.clear(); 67 containedNodeModulesWatchers?.clear(); 68 currentKey = undefined; 69 }, 70 count() { 71 return cache ? cache.size : 0; 72 } 73 }; 74 if (Debug.isDebugging) { 75 Object.defineProperty(result, "__cache", { get: () => cache }); 76 } 77 return result; 78 79 function ensureCache(fromFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions) { 80 const newKey = key(fromFileName, preferences, options); 81 if (cache && (currentKey !== newKey)) { 82 result.clear(); 83 } 84 currentKey = newKey; 85 return cache ||= new Map(); 86 } 87 88 function key(fromFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions) { 89 return `${fromFileName},${preferences.importModuleSpecifierEnding},${preferences.importModuleSpecifierPreference},${options.overrideImportMode}`; 90 } 91 92 function createInfo( 93 modulePaths: readonly ModulePath[] | undefined, 94 moduleSpecifiers: readonly string[] | undefined, 95 isBlockedByPackageJsonDependencies: boolean | undefined, 96 ): ResolvedModuleSpecifierInfo { 97 return { modulePaths, moduleSpecifiers, isBlockedByPackageJsonDependencies }; 98 } 99} 100