@remix-run/assets 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +185 -104
  2. package/dist/assets.d.ts +4 -1
  3. package/dist/assets.d.ts.map +1 -1
  4. package/dist/lib/access.d.ts +29 -2
  5. package/dist/lib/access.d.ts.map +1 -1
  6. package/dist/lib/access.js +52 -26
  7. package/dist/lib/asset-server.d.ts +61 -25
  8. package/dist/lib/asset-server.d.ts.map +1 -1
  9. package/dist/lib/asset-server.js +127 -55
  10. package/dist/lib/compilation-error.d.ts +1 -1
  11. package/dist/lib/compilation-error.d.ts.map +1 -1
  12. package/dist/lib/files/compiler.d.ts +1 -1
  13. package/dist/lib/files/compiler.d.ts.map +1 -1
  14. package/dist/lib/files/compiler.js +28 -31
  15. package/dist/lib/files/config.d.ts +6 -0
  16. package/dist/lib/files/config.d.ts.map +1 -1
  17. package/dist/lib/files/config.js +9 -0
  18. package/dist/lib/fingerprint.d.ts +0 -4
  19. package/dist/lib/fingerprint.d.ts.map +1 -1
  20. package/dist/lib/fingerprint.js +0 -4
  21. package/dist/lib/hmr.d.ts +7 -0
  22. package/dist/lib/hmr.d.ts.map +1 -1
  23. package/dist/lib/hmr.js +198 -25
  24. package/dist/lib/injected-packages.d.ts +3 -2
  25. package/dist/lib/injected-packages.d.ts.map +1 -1
  26. package/dist/lib/injected-packages.js +11 -8
  27. package/dist/lib/inspection.d.ts +39 -0
  28. package/dist/lib/inspection.d.ts.map +1 -0
  29. package/dist/lib/inspection.js +160 -0
  30. package/dist/lib/routes.d.ts +12 -3
  31. package/dist/lib/routes.d.ts.map +1 -1
  32. package/dist/lib/routes.js +124 -80
  33. package/dist/lib/scripts/compiler.d.ts +8 -2
  34. package/dist/lib/scripts/compiler.d.ts.map +1 -1
  35. package/dist/lib/scripts/compiler.js +188 -38
  36. package/dist/lib/scripts/emit.d.ts +2 -1
  37. package/dist/lib/scripts/emit.d.ts.map +1 -1
  38. package/dist/lib/scripts/emit.js +25 -16
  39. package/dist/lib/scripts/resolve.d.ts +7 -1
  40. package/dist/lib/scripts/resolve.d.ts.map +1 -1
  41. package/dist/lib/scripts/resolve.js +119 -12
  42. package/dist/lib/scripts/specifiers.d.ts +2 -0
  43. package/dist/lib/scripts/specifiers.d.ts.map +1 -0
  44. package/dist/lib/scripts/specifiers.js +9 -0
  45. package/dist/lib/scripts/transform.d.ts +2 -3
  46. package/dist/lib/scripts/transform.d.ts.map +1 -1
  47. package/dist/lib/scripts/transform.js +4 -18
  48. package/dist/lib/styles/compiler.d.ts +0 -1
  49. package/dist/lib/styles/compiler.d.ts.map +1 -1
  50. package/dist/lib/styles/compiler.js +105 -25
  51. package/dist/lib/styles/emit.d.ts +2 -1
  52. package/dist/lib/styles/emit.d.ts.map +1 -1
  53. package/dist/lib/styles/emit.js +12 -10
  54. package/dist/lib/styles/resolve.d.ts +0 -1
  55. package/dist/lib/styles/resolve.d.ts.map +1 -1
  56. package/dist/lib/styles/resolve.js +8 -9
  57. package/dist/lib/styles/transform.d.ts +0 -2
  58. package/dist/lib/styles/transform.d.ts.map +1 -1
  59. package/dist/lib/styles/transform.js +2 -9
  60. package/dist/lib/virtual-store.d.ts +8 -0
  61. package/dist/lib/virtual-store.d.ts.map +1 -0
  62. package/dist/lib/virtual-store.js +100 -0
  63. package/package.json +6 -6
  64. package/src/assets.ts +9 -1
  65. package/src/lib/access.ts +83 -30
  66. package/src/lib/asset-server.ts +224 -94
  67. package/src/lib/compilation-error.ts +4 -3
  68. package/src/lib/files/compiler.ts +32 -42
  69. package/src/lib/files/config.ts +17 -0
  70. package/src/lib/fingerprint.ts +0 -9
  71. package/src/lib/hmr.ts +210 -26
  72. package/src/lib/injected-packages.ts +16 -10
  73. package/src/lib/inspection.ts +229 -0
  74. package/src/lib/routes.ts +158 -126
  75. package/src/lib/scripts/compiler.ts +240 -54
  76. package/src/lib/scripts/emit.ts +34 -19
  77. package/src/lib/scripts/resolve.ts +175 -13
  78. package/src/lib/scripts/specifiers.ts +11 -0
  79. package/src/lib/scripts/transform.ts +6 -25
  80. package/src/lib/styles/compiler.ts +137 -39
  81. package/src/lib/styles/emit.ts +18 -13
  82. package/src/lib/styles/resolve.ts +8 -10
  83. package/src/lib/styles/transform.ts +2 -12
  84. package/src/lib/virtual-store.ts +124 -0
@@ -3,6 +3,7 @@ import * as os from 'node:os'
3
3
  import * as path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { IfNoneMatch } from '@remix-run/headers/if-none-match'
6
+ import { getTsconfig } from 'get-tsconfig'
6
7
 
7
8
  import { createAssetServerCompilationError } from '../compilation-error.ts'
8
9
  import { createFileMatcher } from '../file-matcher.ts'
@@ -20,6 +21,7 @@ import {
20
21
  supportedScriptExtensions,
21
22
  } from './resolve.ts'
22
23
  import type { ResolveArgs, ResolvedModule } from './resolve.ts'
24
+ import { isBareImportSpecifier } from './specifiers.ts'
23
25
  import type { CompiledRoutes } from '../routes.ts'
24
26
  import type { ResolvedScriptTarget } from '../target.ts'
25
27
  import { createModuleStore } from '../module-store.ts'
@@ -34,6 +36,7 @@ import type {
34
36
  import { createTsconfigTransformOptionsResolver, transformModule } from './transform.ts'
35
37
  import type { ResolveModuleResult, TransformArgs, TransformedModule } from './transform.ts'
36
38
  import { ResolverFactory } from 'oxc-resolver'
39
+ import type { NapiResolveOptions } from 'oxc-resolver'
37
40
  import type { EmittedAsset, EmittedModule } from './emit.ts'
38
41
 
39
42
  type ScriptRecord = ModuleRecord<TransformedModule, ResolvedModule, EmittedModule>
@@ -45,6 +48,11 @@ type ScriptCompileResult = {
45
48
  sourceMap: EmittedAsset | null
46
49
  }
47
50
 
51
+ export type ScriptImportMap = {
52
+ imports: Record<string, string>
53
+ scopes?: Record<string, Record<string, string>>
54
+ }
55
+
48
56
  type ScriptGetResult =
49
57
  | {
50
58
  script: ScriptCompileResult
@@ -62,7 +70,6 @@ type ScriptGetOptions = {
62
70
  }
63
71
 
64
72
  type ScriptCompilerOptions = {
65
- buildId?: string
66
73
  define?: Record<string, string>
67
74
  external: string[]
68
75
  fingerprintAssets: boolean
@@ -87,7 +94,9 @@ type ScriptCompilerOptions = {
87
94
  type ScriptCompiler = {
88
95
  getScript(filePath: string, options: ScriptGetOptions): Promise<ScriptGetResult>
89
96
  getPreloadLayers(filePath: string | readonly string[]): Promise<string[][]>
97
+ getImportMap(filePath: string | readonly string[]): Promise<ScriptImportMap>
90
98
  getHref(filePath: string): Promise<string>
99
+ resolveSpecifierFromRoot(specifier: string): Promise<string>
91
100
  classifyHmrFileEvent(filePath: string, event: ModuleWatchEvent): Promise<ScriptHmrUpdate[]>
92
101
  invalidateFileEvent(filePath: string, event: ModuleWatchEvent): void
93
102
  parseRequestPathname(pathname: string): ParsedRequestPathname | null
@@ -109,7 +118,8 @@ export type ScriptHmrUpdate =
109
118
  }
110
119
  | {
111
120
  accepted: true
112
- acceptedPath: string
121
+ acceptedFilePath: string
122
+ acceptedUrlPathname: string
113
123
  filePath: string
114
124
  path: string
115
125
  timestamp: number
@@ -120,8 +130,13 @@ type ScriptHmrBoundary = {
120
130
  boundaryModule: ResolvedModule
121
131
  }
122
132
 
133
+ type DirectoryTsconfig = {
134
+ fileIndependent: boolean
135
+ path: string | null
136
+ }
137
+
123
138
  const supportedScriptExtensionSet = new Set<string>(supportedScriptExtensions)
124
- const preloadConcurrency = Math.max(1, Math.min(8, os.availableParallelism() - 1))
139
+ const scriptConcurrency = Math.max(1, Math.min(8, os.availableParallelism() - 1))
125
140
 
126
141
  export function createScriptCompiler(options: ScriptCompilerOptions): ScriptCompiler {
127
142
  let resolvedOptions = {
@@ -146,19 +161,22 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
146
161
  onWatchFilesChange: options.onWatchFilesChange,
147
162
  })
148
163
  let tsconfigTransformOptionsResolver = createTsconfigTransformOptionsResolver()
149
- let resolverFactory = new ResolverFactory({
164
+ let resolverOptions = {
150
165
  aliasFields: [['browser']],
151
166
  conditionNames: ['browser', 'import', 'module', 'default'],
152
167
  extensionAlias: resolverExtensionAlias,
153
168
  extensions: resolverExtensions,
154
169
  mainFields: ['browser', 'module', 'main'],
155
- tsconfig: 'auto',
156
- })
170
+ } satisfies NapiResolveOptions
171
+ let resolverFactory = new ResolverFactory({ ...resolverOptions, tsconfig: 'auto' })
172
+ let directoryResolverByTsconfig = new Map<string | null, ResolverFactory>()
173
+ let tsconfigByDirectory = new Map<string, DirectoryTsconfig>()
174
+ let directoryResolutionIdentityByCacheKey = new Map<string, Promise<string | null>>()
157
175
  let resolveInFlightByCacheKey = new Map<string, Promise<ResolvedModule>>()
158
176
  let emitInFlightByCacheKey = new Map<string, Promise<EmittedModule>>()
177
+ let hasResolvedScripts = false
159
178
 
160
179
  let transformArgs: TransformArgs = {
161
- buildId: resolvedOptions.buildId ?? null,
162
180
  define: resolvedOptions.define ?? null,
163
181
  externalSet: resolvedOptions.externalSet,
164
182
  isWatchIgnored,
@@ -172,10 +190,13 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
172
190
  tsconfigTransformOptionsResolver,
173
191
  }
174
192
  let resolveArgs: ResolveArgs = {
193
+ concurrency: scriptConcurrency,
194
+ isDirectoryResolutionFileIndependent,
175
195
  isAllowed: resolvedOptions.isAllowed,
176
196
  isWatchIgnored,
177
197
  resolveModulePath,
178
198
  resolverFactory,
199
+ resolveDirectorySpecifierIdentity,
179
200
  routes: resolvedOptions.routes,
180
201
  }
181
202
 
@@ -194,17 +215,7 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
194
215
  },
195
216
 
196
217
  async getPreloadLayers(filePath) {
197
- let resolvedEntries: string[] = []
198
- let seen = new Set<string>()
199
-
200
- for (let resolvedModule of (Array.isArray(filePath) ? filePath : [filePath]).map((nextPath) =>
201
- resolveServedScriptOrThrow(resolveInputFilePath(nextPath)),
202
- )) {
203
- if (seen.has(resolvedModule.identityPath)) continue
204
- seen.add(resolvedModule.identityPath)
205
- resolvedEntries.push(resolvedModule.identityPath)
206
- }
207
-
218
+ let resolvedEntries = resolveInputScriptRoots(filePath)
208
219
  let visited = new Set(resolvedEntries)
209
220
  let queue = [...resolvedEntries]
210
221
  let layers: string[][] = []
@@ -218,9 +229,9 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
218
229
  let layer: string[] = []
219
230
 
220
231
  for (let resolvedModule of resolvedModules) {
221
- layer.push(getServedUrlForResolvedScript(resolvedModule))
232
+ layer.push(await getServedUrl(resolvedModule.identityPath))
222
233
 
223
- for (let dep of resolvedModule.deps) {
234
+ for (let dep of resolvedModule.staticDeps) {
224
235
  if (visited.has(dep)) continue
225
236
  visited.add(dep)
226
237
  queue.push(dep)
@@ -233,11 +244,88 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
233
244
  return layers
234
245
  },
235
246
 
247
+ async getImportMap(filePath) {
248
+ let resolvedEntries = resolveInputScriptRoots(filePath)
249
+ let resolvedEntrySet = new Set(resolvedEntries)
250
+ let visited = new Set<string>()
251
+ let queue = [...resolvedEntries]
252
+ let imports: Record<string, string> = {}
253
+ let scopes: Record<string, Record<string, string>> = {}
254
+
255
+ while (queue.length > 0) {
256
+ let frontier = queue
257
+ queue = []
258
+ let resolvedModules = await getOrCreateResolvedScripts(
259
+ frontier.map((identityPath) => scriptStore.get(identityPath)),
260
+ )
261
+
262
+ for (let resolvedModule of resolvedModules) {
263
+ if (visited.has(resolvedModule.identityPath)) continue
264
+ visited.add(resolvedModule.identityPath)
265
+ if (!resolvedEntrySet.has(resolvedModule.identityPath)) {
266
+ addImportMapEntry(
267
+ imports,
268
+ resolvedModule.stableUrlPathname,
269
+ await getServedUrl(resolvedModule.identityPath),
270
+ )
271
+ }
272
+
273
+ for (let imported of resolvedModule.imports) {
274
+ let depUrl = await getServedUrl(imported.depPath)
275
+ if (isBareImportSpecifier(imported.specifier)) {
276
+ let scopePathname = imported.scopePathname
277
+ if (!scopePathname) {
278
+ throw new Error(
279
+ `Expected import map scope for bare import "${imported.specifier}" in ${resolvedModule.identityPath}`,
280
+ )
281
+ }
282
+ let scopeImports = (scopes[scopePathname] ??= {})
283
+ addImportMapEntry(scopeImports, imported.specifier, depUrl)
284
+ } else {
285
+ let browserResolvedSpecifier = resolveImportMapUrlSpecifier(
286
+ imported.specifier,
287
+ resolvedModule.stableUrlPathname,
288
+ )
289
+ addImportMapEntry(imports, browserResolvedSpecifier, depUrl)
290
+ }
291
+ }
292
+
293
+ for (let dep of resolvedModule.deps) {
294
+ if (!visited.has(dep)) {
295
+ queue.push(dep)
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ for (let [scopePathname, scopeImports] of Object.entries(scopes)) {
302
+ if (Object.keys(scopeImports).length === 0) {
303
+ delete scopes[scopePathname]
304
+ }
305
+ }
306
+
307
+ return Object.keys(scopes).length > 0 ? { imports, scopes } : { imports }
308
+ },
309
+
236
310
  async getHref(filePath) {
237
311
  let resolvedModule = resolveServedScriptOrThrow(resolveInputFilePath(filePath))
238
312
  return getServedUrl(resolvedModule.identityPath)
239
313
  },
240
314
 
315
+ async resolveSpecifierFromRoot(specifier) {
316
+ let importerPath = path.join(resolvedOptions.rootDir, '__remix_virtual_entry__.js')
317
+ let resolutionResult = await resolverFactory.resolveFileAsync(importerPath, specifier)
318
+ if (resolutionResult.error || !resolutionResult.path) {
319
+ throw createAssetServerCompilationError(
320
+ `Failed to resolve browser module "${specifier}" from asset server root.`,
321
+ { code: 'IMPORT_RESOLUTION_FAILED' },
322
+ )
323
+ }
324
+
325
+ let resolvedModule = resolveServedScriptOrThrow(resolutionResult.path)
326
+ return getServedUrl(resolvedModule.identityPath)
327
+ },
328
+
241
329
  async classifyHmrFileEvent(filePath, event) {
242
330
  let normalizedFilePath = normalizeFilePath(filePath)
243
331
  if (isWatchIgnored(normalizedFilePath)) return []
@@ -245,9 +333,24 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
245
333
  let timestamp = Date.now()
246
334
  let previousResolvedModule = scriptStore.getLastResolved(normalizedFilePath)
247
335
  let updatePathname = previousResolvedModule?.stableUrlPathname
336
+ let resolutionMetadataChanged =
337
+ isPackageJsonPath(normalizedFilePath) || isTsconfigPath(normalizedFilePath)
248
338
 
249
339
  invalidateScriptFileEvent(normalizedFilePath, event)
250
340
 
341
+ if (resolutionMetadataChanged && hasResolvedScripts) {
342
+ let hmrUpdate: ScriptHmrUpdate[] = [
343
+ {
344
+ accepted: false,
345
+ filePath: normalizedFilePath,
346
+ path: normalizedFilePath,
347
+ timestamp,
348
+ },
349
+ ]
350
+ resolvedOptions.hmr?.send(hmrUpdate)
351
+ return hmrUpdate
352
+ }
353
+
251
354
  let resolvedModule =
252
355
  event === 'change' && updatePathname
253
356
  ? await tryGetOrCreateResolvedScript(scriptStore.get(normalizedFilePath))
@@ -305,6 +408,11 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
305
408
 
306
409
  if (shouldClearResolverCacheForFileEvent(normalizedFilePath, event)) {
307
410
  resolverFactory.clearCache()
411
+ for (let directoryResolver of directoryResolverByTsconfig.values()) {
412
+ directoryResolver.clearCache()
413
+ }
414
+ tsconfigByDirectory.clear()
415
+ directoryResolutionIdentityByCacheKey.clear()
308
416
  }
309
417
 
310
418
  if (isTsconfigPath(normalizedFilePath)) {
@@ -321,6 +429,50 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
321
429
  scriptStore.invalidateForFileEvent(normalizedFilePath, event)
322
430
  }
323
431
 
432
+ function isDirectoryResolutionFileIndependent(directory: string): boolean {
433
+ return getDirectoryTsconfig(directory).fileIndependent
434
+ }
435
+
436
+ function getDirectoryTsconfig(directory: string): DirectoryTsconfig {
437
+ let existing = tsconfigByDirectory.get(directory)
438
+ if (existing) return existing
439
+
440
+ let tsconfig = getTsconfig(directory)
441
+ let result = {
442
+ fileIndependent: !tsconfig?.config.references?.length,
443
+ path: tsconfig?.path ?? null,
444
+ }
445
+ tsconfigByDirectory.set(directory, result)
446
+ return result
447
+ }
448
+
449
+ async function resolveDirectorySpecifierIdentity(
450
+ directory: string,
451
+ specifier: string,
452
+ ): Promise<string | null> {
453
+ let cacheKey = `${directory}\0${specifier}`
454
+ let existing = directoryResolutionIdentityByCacheKey.get(cacheKey)
455
+ if (existing) return existing
456
+
457
+ let promise = (async () => {
458
+ let tsconfigPath = getDirectoryTsconfig(directory).path
459
+ let directoryResolver = directoryResolverByTsconfig.get(tsconfigPath)
460
+ if (!directoryResolver) {
461
+ directoryResolver = new ResolverFactory({
462
+ ...resolverOptions,
463
+ tsconfig: tsconfigPath ? { configFile: tsconfigPath } : undefined,
464
+ })
465
+ directoryResolverByTsconfig.set(tsconfigPath, directoryResolver)
466
+ }
467
+
468
+ let result = await directoryResolver.async(directory, specifier)
469
+ if (!result.path || !path.isAbsolute(result.path)) return null
470
+ return resolveModulePath(normalizeFilePath(result.path))?.identityPath ?? null
471
+ })()
472
+ directoryResolutionIdentityByCacheKey.set(cacheKey, promise)
473
+ return promise
474
+ }
475
+
324
476
  function resolveServedScriptOrThrow(absolutePath: string): ResolveModuleResult {
325
477
  let resolvedModule = resolveModulePath(absolutePath)
326
478
  if (!resolvedModule) {
@@ -342,6 +494,20 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
342
494
  return resolvedModule
343
495
  }
344
496
 
497
+ function resolveInputScriptRoots(filePath: string | readonly string[]): string[] {
498
+ let resolvedEntries: string[] = []
499
+ let seen = new Set<string>()
500
+
501
+ for (let nextPath of Array.isArray(filePath) ? filePath : [filePath]) {
502
+ let resolvedModule = resolveServedScriptOrThrow(resolveInputFilePath(nextPath))
503
+ if (seen.has(resolvedModule.identityPath)) continue
504
+ seen.add(resolvedModule.identityPath)
505
+ resolvedEntries.push(resolvedModule.identityPath)
506
+ }
507
+
508
+ return resolvedEntries
509
+ }
510
+
345
511
  function getNotModifiedScript(
346
512
  record: ScriptRecord,
347
513
  options: ScriptGetOptions,
@@ -363,7 +529,7 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
363
529
  }
364
530
 
365
531
  async function getOrCreateResolvedScripts(records: ScriptRecord[]): Promise<ResolvedModule[]> {
366
- return mapWithConcurrency(records, preloadConcurrency, (record) =>
532
+ return mapWithConcurrency(records, scriptConcurrency, (record) =>
367
533
  getOrCreateResolvedScript(record),
368
534
  )
369
535
  }
@@ -400,6 +566,7 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
400
566
  resolveModuleResult.tracking,
401
567
  ])
402
568
  }
569
+ hasResolvedScripts = true
403
570
 
404
571
  return resolveModuleResult.value
405
572
  })()
@@ -463,10 +630,12 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
463
630
  let promise = (async () => {
464
631
  let startedVersion = record.invalidationVersion
465
632
  let resolvedModule = await getOrCreateResolvedScript(record)
633
+ await resolveScriptGraph(resolvedModule)
466
634
  let emitResolvedModuleResult = await emitResolvedModule(resolvedModule, {
635
+ fingerprintAssets: resolvedOptions.fingerprintAssets,
636
+ getHmrImportTimestamp,
467
637
  getServedUrl,
468
638
  getStableUrl,
469
- getHmrImportTimestamp,
470
639
  hmrClientPathname: resolvedOptions.hmr?.clientPathname,
471
640
  sourceMaps: resolvedOptions.sourceMaps,
472
641
  })
@@ -497,26 +666,47 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
497
666
  }
498
667
  }
499
668
 
500
- async function getServedUrl(identityPath: string): Promise<string> {
501
- return getServedUrlForResolvedScript(
502
- await getOrCreateResolvedScript(scriptStore.get(identityPath)),
503
- )
669
+ async function resolveScriptGraph(root: ResolvedModule): Promise<void> {
670
+ let visited = new Set([root.identityPath])
671
+ let queue = [...root.deps]
672
+
673
+ while (queue.length > 0) {
674
+ let frontier = queue
675
+ queue = []
676
+ let resolvedModules = await getOrCreateResolvedScripts(
677
+ frontier
678
+ .filter((identityPath) => !visited.has(identityPath))
679
+ .map((identityPath) => scriptStore.get(identityPath)),
680
+ )
681
+
682
+ for (let resolvedModule of resolvedModules) {
683
+ if (visited.has(resolvedModule.identityPath)) continue
684
+ visited.add(resolvedModule.identityPath)
685
+
686
+ for (let dep of resolvedModule.deps) {
687
+ if (!visited.has(dep)) queue.push(dep)
688
+ }
689
+ }
690
+ }
504
691
  }
505
692
 
506
- function getServedUrlForResolvedScript(resolvedModule: ResolvedModule): string {
507
- return formatFingerprintedPathname(
508
- resolvedModule.stableUrlPathname,
509
- resolvedOptions.fingerprintAssets ? resolvedModule.fingerprint : null,
510
- )
693
+ async function getServedUrl(identityPath: string): Promise<string> {
694
+ let resolvedModule = await getOrCreateResolvedScript(scriptStore.get(identityPath))
695
+ if (!resolvedOptions.fingerprintAssets) {
696
+ return resolvedModule.stableUrlPathname
697
+ }
698
+
699
+ let emittedModule = await getOrCreateEmittedScript(scriptStore.get(identityPath))
700
+ return formatFingerprintedPathname(resolvedModule.stableUrlPathname, emittedModule.fingerprint)
511
701
  }
512
702
 
513
703
  function getStableUrl(identityPath: string): string {
514
704
  let stableUrlPathname = resolvedOptions.routes.toUrlPathname(identityPath)
515
705
  if (!stableUrlPathname) {
516
706
  throw createAssetServerCompilationError(
517
- `File ${identityPath} is outside all configured fileMap entries.`,
707
+ `File ${identityPath} is outside all configured mounts.`,
518
708
  {
519
- code: 'FILE_OUTSIDE_FILE_MAP',
709
+ code: 'FILE_OUTSIDE_MOUNTS',
520
710
  },
521
711
  )
522
712
  }
@@ -545,7 +735,8 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
545
735
  return [
546
736
  {
547
737
  accepted: true,
548
- acceptedPath: updatePathname,
738
+ acceptedFilePath: resolvedModule.identityPath,
739
+ acceptedUrlPathname: updatePathname,
549
740
  filePath: resolvedModule.identityPath,
550
741
  path: updatePathname,
551
742
  timestamp,
@@ -558,7 +749,8 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
558
749
  if (sourceFilePath !== undefined && boundaries) {
559
750
  return dedupeHmrBoundaries(boundaries).map(({ acceptedModule, boundaryModule }) => ({
560
751
  accepted: true,
561
- acceptedPath: acceptedModule.stableUrlPathname,
752
+ acceptedFilePath: acceptedModule.identityPath,
753
+ acceptedUrlPathname: acceptedModule.stableUrlPathname,
562
754
  filePath: sourceFilePath,
563
755
  path: boundaryModule.stableUrlPathname,
564
756
  timestamp,
@@ -630,6 +822,15 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
630
822
  }
631
823
  }
632
824
 
825
+ function resolveImportMapUrlSpecifier(specifier: string, importerUrlPathname: string): string {
826
+ return new URL(specifier, `http://localhost${importerUrlPathname}`).pathname
827
+ }
828
+
829
+ function addImportMapEntry(imports: Record<string, string>, specifier: string, url: string): void {
830
+ if (specifier === url) return
831
+ imports[specifier] = url
832
+ }
833
+
633
834
  function dedupeHmrBoundaries(boundaries: ScriptHmrBoundary[]): ScriptHmrBoundary[] {
634
835
  let seen = new Set<string>()
635
836
  let result: ScriptHmrBoundary[] = []
@@ -658,16 +859,13 @@ function getNotModifiedResult(
658
859
  ): ScriptGetResult | null {
659
860
  if (!emittedModule || options.ifNoneMatch === null) return null
660
861
 
661
- if (
662
- options.requestedFingerprint !== null &&
663
- emittedModule.fingerprint !== options.requestedFingerprint
664
- ) {
665
- return null
666
- }
667
-
668
862
  let asset = getEmittedAssetForRequest(emittedModule, options.isSourceMapRequest)
669
863
  if (!asset) return null
670
864
 
865
+ if (options.requestedFingerprint !== null && asset.fingerprint !== options.requestedFingerprint) {
866
+ return null
867
+ }
868
+
671
869
  if (!IfNoneMatch.from(options.ifNoneMatch).matches(asset.etag)) return null
672
870
  return { type: 'not-modified', etag: asset.etag }
673
871
  }
@@ -801,18 +999,6 @@ function resolveActualPath(identityPath: string): string | null {
801
999
  }
802
1000
  }
803
1001
 
804
- function isBareImportSpecifier(specifier: string): boolean {
805
- return (
806
- !specifier.startsWith('./') &&
807
- !specifier.startsWith('../') &&
808
- !specifier.startsWith('/') &&
809
- !specifier.startsWith('file:') &&
810
- !specifier.startsWith('data:') &&
811
- !specifier.startsWith('http://') &&
812
- !specifier.startsWith('https://')
813
- )
814
- }
815
-
816
1002
  function isNoEntityError(
817
1003
  error: unknown,
818
1004
  ): error is NodeJS.ErrnoException & { code: 'ENOENT' | 'ENOTDIR' } {
@@ -4,7 +4,7 @@ import {
4
4
  createAssetServerCompilationError,
5
5
  isAssetServerCompilationError,
6
6
  } from '../compilation-error.ts'
7
- import { hashContent } from '../fingerprint.ts'
7
+ import { formatFingerprintedPathname, hashContent } from '../fingerprint.ts'
8
8
  import type { ResolvedModule } from './resolve.ts'
9
9
  import { composeSourceMaps } from '../source-maps.ts'
10
10
  import type { AssetServerCompilationError } from '../compilation-error.ts'
@@ -12,12 +12,12 @@ import type { AssetServerCompilationError } from '../compilation-error.ts'
12
12
  export type EmittedAsset = {
13
13
  content: string
14
14
  etag: string
15
+ fingerprint: string
15
16
  }
16
17
 
17
18
  export type EmittedModule = {
18
19
  code: EmittedAsset
19
20
  fingerprint: string | null
20
- importUrls: string[]
21
21
  sourceMap: EmittedAsset | null
22
22
  }
23
23
 
@@ -40,6 +40,7 @@ type RewriteImportsOptions = {
40
40
  export async function emitResolvedModule(
41
41
  resolvedModule: ResolvedModule,
42
42
  options: {
43
+ fingerprintAssets: boolean
43
44
  getHmrImportTimestamp(identityPath: string): number | null
44
45
  getServedUrl(identityPath: string): Promise<string>
45
46
  getStableUrl(identityPath: string): string
@@ -48,30 +49,32 @@ export async function emitResolvedModule(
48
49
  },
49
50
  ): Promise<EmitResult> {
50
51
  try {
51
- let importUrls = await Promise.all(
52
- resolvedModule.deps.map((depPath) => options.getServedUrl(depPath)),
53
- )
54
52
  let rewriteResult = await rewriteImports(resolvedModule, options)
55
53
  let finalCode = prependHmrContext(resolvedModule, rewriteResult.code, options)
54
+ let sourceMap = rewriteResult.sourceMap
55
+ ? await createEmittedAsset(rewriteResult.sourceMap)
56
+ : null
56
57
 
57
58
  if (rewriteResult.sourceMap) {
58
59
  if (options.sourceMaps === 'inline') {
59
60
  let encoded = Buffer.from(rewriteResult.sourceMap).toString('base64')
60
61
  finalCode += `\n//# sourceMappingURL=data:application/json;base64,${encoded}`
61
62
  } else if (options.sourceMaps === 'external') {
62
- finalCode += `\n//# sourceMappingURL=${await options.getServedUrl(resolvedModule.identityPath)}.map`
63
+ finalCode += `\n//# sourceMappingURL=${formatFingerprintedPathname(
64
+ resolvedModule.stableUrlPathname,
65
+ options.fingerprintAssets && sourceMap ? sourceMap.fingerprint : null,
66
+ )}.map`
63
67
  }
64
68
  }
65
69
 
70
+ let code = await createEmittedAsset(finalCode)
71
+
66
72
  return {
67
73
  ok: true,
68
74
  value: {
69
- code: await createEmittedAsset(finalCode),
70
- fingerprint: resolvedModule.fingerprint,
71
- importUrls,
72
- sourceMap: rewriteResult.sourceMap
73
- ? await createEmittedAsset(rewriteResult.sourceMap)
74
- : null,
75
+ code,
76
+ fingerprint: options.fingerprintAssets ? code.fingerprint : null,
77
+ sourceMap,
75
78
  },
76
79
  }
77
80
  } catch (error) {
@@ -87,18 +90,28 @@ async function rewriteImports(
87
90
  options: RewriteImportsOptions,
88
91
  ): Promise<{ code: string; sourceMap: string | null }> {
89
92
  let rewrittenSource = new MagicString(resolvedModule.rawCode)
93
+ let changed = false
90
94
 
91
95
  for (let imported of resolvedModule.imports) {
92
- let url = await options.getServedUrl(imported.depPath)
93
96
  let hmrImportTimestamp = options.getHmrImportTimestamp(imported.depPath)
97
+ let replacementSpecifier = imported.specifier
94
98
  if (hmrImportTimestamp !== null) {
95
- url = addTimestampQuery(url, hmrImportTimestamp)
99
+ replacementSpecifier = addTimestampQuery(
100
+ await options.getServedUrl(imported.depPath),
101
+ hmrImportTimestamp,
102
+ )
103
+ } else if (imported.compiledSpecifier === imported.specifier) {
104
+ continue
96
105
  }
106
+
97
107
  rewrittenSource.overwrite(
98
108
  imported.start,
99
109
  imported.end,
100
- imported.quote ? `${imported.quote}${url}${imported.quote}` : url,
110
+ imported.quote
111
+ ? `${imported.quote}${replacementSpecifier}${imported.quote}`
112
+ : replacementSpecifier,
101
113
  )
114
+ changed = true
102
115
  }
103
116
 
104
117
  for (let acceptedDep of resolvedModule.hmr.acceptedDeps) {
@@ -108,12 +121,12 @@ async function rewriteImports(
108
121
  acceptedDep.end,
109
122
  acceptedDep.quote ? `${acceptedDep.quote}${url}${acceptedDep.quote}` : url,
110
123
  )
124
+ changed = true
111
125
  }
112
126
 
113
- let code = rewrittenSource.toString()
127
+ let code = changed ? rewrittenSource.toString() : resolvedModule.rawCode
114
128
  let sourceMap =
115
- resolvedModule.sourceMap &&
116
- (resolvedModule.imports.length > 0 || resolvedModule.hmr.acceptedDeps.length > 0)
129
+ resolvedModule.sourceMap && changed
117
130
  ? composeSourceMaps(
118
131
  rewrittenSource.generateMap({ hires: true }).toString(),
119
132
  resolvedModule.sourceMap,
@@ -148,9 +161,11 @@ function prependHmrContext(
148
161
  }
149
162
 
150
163
  async function createEmittedAsset(content: string): Promise<EmittedAsset> {
164
+ let fingerprint = await hashContent(content)
151
165
  return {
152
166
  content,
153
- etag: `W/"${await hashContent(content)}"`,
167
+ etag: `W/"${fingerprint}"`,
168
+ fingerprint,
154
169
  }
155
170
  }
156
171