@@ -11,12 +11,12 @@ import { ensureRealSvelteFilePath, isSvelteFilePath, isVirtualSvelteFilePath } f
11
11
class ModuleResolutionCache {
12
12
constructor ( private readonly projectService : ts . server . ProjectService ) { }
13
13
14
- private cache = new Map < string , ts . ResolvedModuleFull > ( ) ;
14
+ private cache = new Map < string , ts . ResolvedModuleFull | null > ( ) ;
15
15
16
16
/**
17
17
* Tries to get a cached module.
18
18
*/
19
- get ( moduleName : string , containingFile : string ) : ts . ResolvedModuleFull | undefined {
19
+ get ( moduleName : string , containingFile : string ) : ts . ResolvedModuleFull | null | undefined {
20
20
return this . cache . get ( this . getKey ( moduleName , containingFile ) ) ;
21
21
}
22
22
@@ -28,10 +28,14 @@ class ModuleResolutionCache {
28
28
containingFile : string ,
29
29
resolvedModule : ts . ResolvedModuleFull | undefined
30
30
) {
31
- if ( ! resolvedModule ) {
31
+ if ( ! resolvedModule && moduleName [ 0 ] === '.' ) {
32
+ // We cache unresolved modules for non-relative imports, too, because it's very likely that they don't change
33
+ // and we don't want to resolve them every time. If they do change, the original resolution mode will notice
34
+ // most of the time, and the only time this would result in a stale cache entry is if a node_modules package
35
+ // is added with a "svelte" condition and no "types" condition, which is rare enough.
32
36
return ;
33
37
}
34
- this . cache . set ( this . getKey ( moduleName , containingFile ) , resolvedModule ) ;
38
+ this . cache . set ( this . getKey ( moduleName , containingFile ) , resolvedModule ?? null ) ;
35
39
}
36
40
37
41
/**
@@ -42,6 +46,7 @@ class ModuleResolutionCache {
42
46
resolvedModuleName = this . projectService . toCanonicalFileName ( resolvedModuleName ) ;
43
47
this . cache . forEach ( ( val , key ) => {
44
48
if (
49
+ val &&
45
50
this . projectService . toCanonicalFileName ( val . resolvedFileName ) === resolvedModuleName
46
51
) {
47
52
this . cache . delete ( key ) ;
@@ -87,6 +92,8 @@ export function patchModuleLoader(
87
92
if ( lsHost . resolveModuleNameLiterals ) {
88
93
lsHost . resolveModuleNameLiterals = resolveModuleNameLiterals ;
89
94
} else {
95
+ // TODO do we need to keep this around? We're requiring 5.0 now, so TS doesn't need it,
96
+ // but would this break when other TS plugins are used and we no longer provide it?
90
97
lsHost . resolveModuleNames = resolveModuleNames ;
91
98
}
92
99
@@ -139,7 +146,9 @@ export function patchModuleLoader(
139
146
return resolved . map ( ( tsResolvedModule , idx ) => {
140
147
const moduleName = moduleNames [ idx ] ;
141
148
if (
142
- ! isSvelteFilePath ( moduleName ) ||
149
+ // Only recheck relative Svelte imports or unresolved non-relative paths (which hint at node_modules,
150
+ // where an exports map with "svelte" but not "types" could be present)
151
+ ( ! isSvelteFilePath ( moduleName ) && ( moduleName [ 0 ] === '.' || tsResolvedModule ) ) ||
143
152
// corresponding .d.ts files take precedence over .svelte files
144
153
tsResolvedModule ?. resolvedFileName . endsWith ( '.d.ts' ) ||
145
154
tsResolvedModule ?. resolvedFileName . endsWith ( '.d.svelte.ts' )
@@ -165,7 +174,8 @@ export function patchModuleLoader(
165
174
const svelteResolvedModule = typescript . resolveModuleName (
166
175
name ,
167
176
containingFile ,
168
- compilerOptions ,
177
+ // customConditions makes the TS algorithm look at the "svelte" condition in exports maps
178
+ { ...compilerOptions , customConditions : [ 'svelte' ] } ,
169
179
svelteSys
170
180
// don't set mode or else .svelte imports couldn't be resolved
171
181
) . resolvedModule ;
@@ -225,19 +235,22 @@ export function patchModuleLoader(
225
235
226
236
return resolved . map ( ( tsResolvedModule , idx ) => {
227
237
const moduleName = moduleLiterals [ idx ] . text ;
238
+ const resolvedModule = tsResolvedModule . resolvedModule ;
228
239
229
240
if (
230
- ! isSvelteFilePath ( moduleName ) ||
241
+ // Only recheck relative Svelte imports or unresolved non-relative paths (which hint at node_modules,
242
+ // where an exports map with "svelte" but not "types" could be present)
243
+ ( ! isSvelteFilePath ( moduleName ) && ( moduleName [ 0 ] === '.' || resolvedModule ) ) ||
231
244
// corresponding .d.ts files take precedence over .svelte files
232
- tsResolvedModule ?. resolvedModule ?. resolvedFileName . endsWith ( '.d.ts' ) ||
233
- tsResolvedModule ?. resolvedModule ?. resolvedFileName . endsWith ( '.d.svelte.ts' )
245
+ resolvedModule ?. resolvedFileName . endsWith ( '.d.ts' ) ||
246
+ resolvedModule ?. resolvedFileName . endsWith ( '.d.svelte.ts' )
234
247
) {
235
248
return tsResolvedModule ;
236
249
}
237
250
238
251
const result = resolveSvelteModuleNameFromCache ( moduleName , containingFile , options ) ;
239
252
// .svelte takes precedence over .svelte.ts etc
240
- return result ?? tsResolvedModule ;
253
+ return result . resolvedModule ? result : tsResolvedModule ;
241
254
} ) ;
242
255
}
243
256
@@ -247,13 +260,29 @@ export function patchModuleLoader(
247
260
options : ts . CompilerOptions
248
261
) {
249
262
const cachedModule = moduleCache . get ( moduleName , containingFile ) ;
250
- if ( cachedModule ) {
263
+ if ( typeof cachedModule === 'object' ) {
251
264
return {
252
- resolvedModule : cachedModule
265
+ resolvedModule : cachedModule ?? undefined
253
266
} ;
254
267
}
255
268
256
269
const resolvedModule = resolveSvelteModuleName ( moduleName , containingFile , options ) ;
270
+
271
+ // Align with TypeScript behavior: If the Svelte file is not using TypeScript,
272
+ // mark it as unresolved so that people need to provide a .d.ts file.
273
+ // For backwards compatibility we're not doing this for files from packages
274
+ // without an exports map, because that may break too many existing projects.
275
+ if (
276
+ resolvedModule ?. isExternalLibraryImport && // TODO how to check this is not from a non-exports map?
277
+ // TODO check what happens if this resolves to a real .d.svelte.ts file
278
+ resolvedModule . extension === '.ts' // this tells us it's from an exports map
279
+ ) {
280
+ moduleCache . set ( moduleName , containingFile , undefined ) ;
281
+ return {
282
+ resolvedModule : undefined
283
+ } ;
284
+ }
285
+
257
286
moduleCache . set ( moduleName , containingFile , resolvedModule ) ;
258
287
return {
259
288
resolvedModule : resolvedModule
0 commit comments