|
| 1 | +From 7eb7016049c4fcbfada86ac4c5a1383847ee9844 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Cam McHenry <camchenry@users.noreply.github.com> |
| 3 | +Date: Mon, 2 Mar 2026 21:38:25 -0500 |
| 4 | +Subject: [PATCH] perf: add per-directory module resolution cache |
| 5 | + |
| 6 | +Add a per-directory cache to ResolveModuleName and |
| 7 | +ResolveTypeReferenceDirective. Files in the same directory resolving the |
| 8 | +same module name with the same resolution mode and project reference |
| 9 | +redirect will always produce the same result, so we can cache and reuse |
| 10 | +the resolution result. |
| 11 | +--- |
| 12 | + internal/module/cache.go | 72 ++++++++++++++++++++++++++++++++++++- |
| 13 | + internal/module/resolver.go | 45 ++++++++++++++++++++--- |
| 14 | + 2 files changed, 112 insertions(+), 5 deletions(-) |
| 15 | + |
| 16 | +diff --git a/internal/module/cache.go b/internal/module/cache.go |
| 17 | +index bddce784ef..5dbe032bd5 100644 |
| 18 | +--- a/internal/module/cache.go |
| 19 | ++++ b/internal/module/cache.go |
| 20 | +@@ -3,15 +3,74 @@ package module |
| 21 | + import ( |
| 22 | + "sync" |
| 23 | + |
| 24 | ++ "github.com/microsoft/typescript-go/internal/collections" |
| 25 | + "github.com/microsoft/typescript-go/internal/core" |
| 26 | + "github.com/microsoft/typescript-go/internal/packagejson" |
| 27 | + ) |
| 28 | + |
| 29 | + type ModeAwareCache[T any] map[ModeAwareCacheKey]T |
| 30 | + |
| 31 | ++type moduleResolutionCacheKey struct { |
| 32 | ++ containingDirectory string |
| 33 | ++ moduleName string |
| 34 | ++ resolutionMode core.ResolutionMode |
| 35 | ++ redirectConfigName string |
| 36 | ++} |
| 37 | ++ |
| 38 | ++type moduleResolutionCache struct { |
| 39 | ++ cache collections.SyncMap[moduleResolutionCacheKey, *ResolvedModule] |
| 40 | ++} |
| 41 | ++ |
| 42 | ++func newModuleResolutionCache() *moduleResolutionCache { |
| 43 | ++ return &moduleResolutionCache{ |
| 44 | ++ cache: collections.SyncMap[moduleResolutionCacheKey, *ResolvedModule]{}, |
| 45 | ++ } |
| 46 | ++} |
| 47 | ++ |
| 48 | ++func (c *moduleResolutionCache) Get(key moduleResolutionCacheKey) (*ResolvedModule, bool) { |
| 49 | ++ return c.cache.Load(key) |
| 50 | ++} |
| 51 | ++ |
| 52 | ++func (c *moduleResolutionCache) Set(key moduleResolutionCacheKey, value *ResolvedModule) { |
| 53 | ++ c.cache.Store(key, value) |
| 54 | ++} |
| 55 | ++ |
| 56 | ++type typeRefDirectiveResolutionCacheKey struct { |
| 57 | ++ containingDirectory string |
| 58 | ++ typeReferenceName string |
| 59 | ++ resolutionMode core.ResolutionMode |
| 60 | ++ redirectConfigName string |
| 61 | ++ fromInferredTypesContainingFile bool |
| 62 | ++} |
| 63 | ++ |
| 64 | ++type typeRefDirectiveResolutionCache struct { |
| 65 | ++ cache collections.SyncMap[typeRefDirectiveResolutionCacheKey, *ResolvedTypeReferenceDirective] |
| 66 | ++} |
| 67 | ++ |
| 68 | ++func newTypeRefDirectiveResolutionCache() *typeRefDirectiveResolutionCache { |
| 69 | ++ return &typeRefDirectiveResolutionCache{ |
| 70 | ++ cache: collections.SyncMap[typeRefDirectiveResolutionCacheKey, *ResolvedTypeReferenceDirective]{}, |
| 71 | ++ } |
| 72 | ++} |
| 73 | ++ |
| 74 | ++func (c *typeRefDirectiveResolutionCache) Get(key typeRefDirectiveResolutionCacheKey) (*ResolvedTypeReferenceDirective, bool) { |
| 75 | ++ return c.cache.Load(key) |
| 76 | ++} |
| 77 | ++ |
| 78 | ++func (c *typeRefDirectiveResolutionCache) Set(key typeRefDirectiveResolutionCacheKey, value *ResolvedTypeReferenceDirective) { |
| 79 | ++ c.cache.Store(key, value) |
| 80 | ++} |
| 81 | ++ |
| 82 | + type caches struct { |
| 83 | + packageJsonInfoCache *packagejson.InfoCache |
| 84 | + |
| 85 | ++ // Per-directory module resolution cache. |
| 86 | ++ // Avoids re-resolving the same module name from the same directory. |
| 87 | ++ moduleResolutionCache *moduleResolutionCache |
| 88 | ++ |
| 89 | ++ // Per-directory type reference directive resolution cache. |
| 90 | ++ typeRefDirectiveResolutionCache *typeRefDirectiveResolutionCache |
| 91 | ++ |
| 92 | + // Cached representation for `core.CompilerOptions.paths`. |
| 93 | + // Doesn't handle other path patterns like in `typesVersions`. |
| 94 | + parsedPatternsForPathsOnce sync.Once |
| 95 | +@@ -24,6 +83,17 @@ func newCaches( |
| 96 | + options *core.CompilerOptions, |
| 97 | + ) caches { |
| 98 | + return caches{ |
| 99 | +- packageJsonInfoCache: packagejson.NewInfoCache(currentDirectory, useCaseSensitiveFileNames), |
| 100 | ++ packageJsonInfoCache: packagejson.NewInfoCache(currentDirectory, useCaseSensitiveFileNames), |
| 101 | ++ moduleResolutionCache: newModuleResolutionCache(), |
| 102 | ++ typeRefDirectiveResolutionCache: newTypeRefDirectiveResolutionCache(), |
| 103 | ++ } |
| 104 | ++} |
| 105 | ++ |
| 106 | ++// getRedirectConfigName returns a stable string key for a ResolvedProjectReference. |
| 107 | ++// Returns "" for nil redirects. |
| 108 | ++func getRedirectConfigName(redirect ResolvedProjectReference) string { |
| 109 | ++ if redirect == nil { |
| 110 | ++ return "" |
| 111 | + } |
| 112 | ++ return redirect.ConfigName() |
| 113 | + } |
| 114 | +diff --git a/internal/module/resolver.go b/internal/module/resolver.go |
| 115 | +index d22fa0a97b..d9034cca5b 100644 |
| 116 | +--- a/internal/module/resolver.go |
| 117 | ++++ b/internal/module/resolver.go |
| 118 | +@@ -196,10 +196,27 @@ func (r *Resolver) ResolveTypeReferenceDirective( |
| 119 | + resolutionMode core.ResolutionMode, |
| 120 | + redirectedReference ResolvedProjectReference, |
| 121 | + ) (*ResolvedTypeReferenceDirective, []DiagAndArgs) { |
| 122 | ++ containingDirectory := tspath.GetDirectoryPath(containingFile) |
| 123 | + traceBuilder := r.newTraceBuilder() |
| 124 | + |
| 125 | ++ fromInferredTypesContainingFile := strings.HasSuffix(containingFile, InferredTypesContainingFile) |
| 126 | ++ |
| 127 | ++ cacheKey := typeRefDirectiveResolutionCacheKey{ |
| 128 | ++ containingDirectory: containingDirectory, |
| 129 | ++ typeReferenceName: typeReferenceDirectiveName, |
| 130 | ++ resolutionMode: resolutionMode, |
| 131 | ++ redirectConfigName: getRedirectConfigName(redirectedReference), |
| 132 | ++ fromInferredTypesContainingFile: fromInferredTypesContainingFile, |
| 133 | ++ } |
| 134 | ++ |
| 135 | ++ // Only use type reference directive resolution cache when trace resolution is disabled |
| 136 | ++ if traceBuilder == nil { |
| 137 | ++ if cached, ok := r.typeRefDirectiveResolutionCache.Get(cacheKey); ok { |
| 138 | ++ return cached, nil |
| 139 | ++ } |
| 140 | ++ } |
| 141 | ++ |
| 142 | + compilerOptions := GetCompilerOptionsWithRedirect(r.compilerOptions, redirectedReference) |
| 143 | +- containingDirectory := tspath.GetDirectoryPath(containingFile) |
| 144 | + |
| 145 | + typeRoots, fromConfig := compilerOptions.GetEffectiveTypeRoots(r.host.GetCurrentDirectory()) |
| 146 | + if traceBuilder != nil { |
| 147 | +@@ -208,22 +225,39 @@ func (r *Resolver) ResolveTypeReferenceDirective( |
| 148 | + } |
| 149 | + |
| 150 | + state := newResolutionState(typeReferenceDirectiveName, containingDirectory, true /*isTypeReferenceDirective*/, resolutionMode, compilerOptions, redirectedReference, r, traceBuilder) |
| 151 | +- result := state.resolveTypeReferenceDirective(typeRoots, fromConfig, strings.HasSuffix(containingFile, InferredTypesContainingFile)) |
| 152 | ++ result := state.resolveTypeReferenceDirective(typeRoots, fromConfig, fromInferredTypesContainingFile) |
| 153 | + |
| 154 | + if traceBuilder != nil { |
| 155 | + traceBuilder.traceTypeReferenceDirectiveResult(typeReferenceDirectiveName, result) |
| 156 | + } |
| 157 | ++ |
| 158 | ++ r.typeRefDirectiveResolutionCache.Set(cacheKey, result) |
| 159 | ++ |
| 160 | + return result, traceBuilder.getTraces() |
| 161 | + } |
| 162 | + |
| 163 | + func (r *Resolver) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode, redirectedReference ResolvedProjectReference) (*ResolvedModule, []DiagAndArgs) { |
| 164 | ++ containingDirectory := tspath.GetDirectoryPath(containingFile) |
| 165 | + traceBuilder := r.newTraceBuilder() |
| 166 | ++ |
| 167 | ++ cacheKey := moduleResolutionCacheKey{ |
| 168 | ++ containingDirectory: containingDirectory, |
| 169 | ++ moduleName: moduleName, |
| 170 | ++ resolutionMode: resolutionMode, |
| 171 | ++ redirectConfigName: getRedirectConfigName(redirectedReference), |
| 172 | ++ } |
| 173 | ++ // Only use module resolution cache when trace resolution is disabled |
| 174 | ++ if traceBuilder == nil { |
| 175 | ++ if cached, ok := r.moduleResolutionCache.Get(cacheKey); ok { |
| 176 | ++ return cached, nil |
| 177 | ++ } |
| 178 | ++ } |
| 179 | ++ |
| 180 | + compilerOptions := GetCompilerOptionsWithRedirect(r.compilerOptions, redirectedReference) |
| 181 | + if traceBuilder != nil { |
| 182 | + traceBuilder.write(diagnostics.Resolving_module_0_from_1, moduleName, containingFile) |
| 183 | + traceBuilder.traceResolutionUsingProjectReference(redirectedReference) |
| 184 | + } |
| 185 | +- containingDirectory := tspath.GetDirectoryPath(containingFile) |
| 186 | + |
| 187 | + moduleResolution := compilerOptions.GetModuleResolutionKind() |
| 188 | + if compilerOptions.ModuleResolution != moduleResolution { |
| 189 | +@@ -257,7 +291,10 @@ func (r *Resolver) ResolveModuleName(moduleName string, containingFile string, r |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | +- return r.tryResolveFromTypingsLocation(moduleName, containingDirectory, result, traceBuilder), traceBuilder.getTraces() |
| 194 | ++ finalResult := r.tryResolveFromTypingsLocation(moduleName, containingDirectory, result, traceBuilder) |
| 195 | ++ r.moduleResolutionCache.Set(cacheKey, finalResult) |
| 196 | ++ |
| 197 | ++ return finalResult, traceBuilder.getTraces() |
| 198 | + } |
| 199 | + |
| 200 | + func (r *Resolver) ResolvePackageDirectory(moduleName string, containingFile string, resolutionMode core.ResolutionMode, redirectedReference ResolvedProjectReference) *ResolvedModule { |
| 201 | +-- |
| 202 | +2.39.5 (Apple Git-154) |
| 203 | + |
0 commit comments