Skip to content

Commit 69acd77

Browse files
committed
perf: [wip] add per-directory module resolution cache to typescript-go
1 parent 9e0ccc1 commit 69acd77

File tree

1 file changed

+203
-0
lines changed

1 file changed

+203
-0
lines changed
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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

Comments
 (0)