@remix-run/assets 0.4.4 → 0.5.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 (74) hide show
  1. package/README.md +322 -56
  2. package/dist/assets.d.ts +2 -1
  3. package/dist/assets.d.ts.map +1 -1
  4. package/dist/assets.js +2 -2
  5. package/dist/lib/access.d.ts +6 -2
  6. package/dist/lib/access.d.ts.map +1 -1
  7. package/dist/lib/access.js +300 -5
  8. package/dist/lib/asset-server.d.ts +118 -5
  9. package/dist/lib/asset-server.d.ts.map +1 -1
  10. package/dist/lib/asset-server.js +260 -24
  11. package/dist/lib/compilation-error.d.ts +1 -1
  12. package/dist/lib/compilation-error.d.ts.map +1 -1
  13. package/dist/lib/file-matcher.js +1 -1
  14. package/dist/lib/files/compiler.d.ts.map +1 -1
  15. package/dist/lib/files/compiler.js +7 -6
  16. package/dist/lib/files/config.js +1 -1
  17. package/dist/lib/hmr.d.ts +37 -0
  18. package/dist/lib/hmr.d.ts.map +1 -0
  19. package/dist/lib/hmr.js +357 -0
  20. package/dist/lib/injected-packages.d.ts.map +1 -1
  21. package/dist/lib/injected-packages.js +36 -13
  22. package/dist/lib/loaders.d.ts +57 -0
  23. package/dist/lib/loaders.d.ts.map +1 -0
  24. package/dist/lib/loaders.js +1 -0
  25. package/dist/lib/module-store.d.ts +24 -0
  26. package/dist/lib/module-store.d.ts.map +1 -1
  27. package/dist/lib/module-store.js +136 -11
  28. package/dist/lib/routes.js +1 -1
  29. package/dist/lib/scripts/compiler.d.ts +24 -1
  30. package/dist/lib/scripts/compiler.d.ts.map +1 -1
  31. package/dist/lib/scripts/compiler.js +183 -29
  32. package/dist/lib/scripts/conditions.d.ts +2 -0
  33. package/dist/lib/scripts/conditions.d.ts.map +1 -0
  34. package/dist/lib/scripts/conditions.js +1 -0
  35. package/dist/lib/scripts/emit.d.ts +3 -0
  36. package/dist/lib/scripts/emit.d.ts.map +1 -1
  37. package/dist/lib/scripts/emit.js +24 -5
  38. package/dist/lib/scripts/resolve.d.ts +4 -0
  39. package/dist/lib/scripts/resolve.d.ts.map +1 -1
  40. package/dist/lib/scripts/resolve.js +70 -5
  41. package/dist/lib/scripts/transform.d.ts +9 -0
  42. package/dist/lib/scripts/transform.d.ts.map +1 -1
  43. package/dist/lib/scripts/transform.js +222 -18
  44. package/dist/lib/source-maps.js +1 -1
  45. package/dist/lib/styles/compiler.d.ts +14 -1
  46. package/dist/lib/styles/compiler.d.ts.map +1 -1
  47. package/dist/lib/styles/compiler.js +78 -14
  48. package/dist/lib/styles/emit.js +4 -4
  49. package/dist/lib/styles/resolve.d.ts.map +1 -1
  50. package/dist/lib/styles/resolve.js +8 -7
  51. package/dist/lib/styles/transform.js +3 -3
  52. package/dist/lib/target.d.ts +1 -1
  53. package/dist/lib/target.d.ts.map +1 -1
  54. package/dist/lib/watch.js +1 -1
  55. package/dist/types/hmr.d.ts +36 -0
  56. package/package.json +17 -11
  57. package/src/assets.ts +2 -1
  58. package/src/lib/access.ts +386 -5
  59. package/src/lib/asset-server.ts +429 -18
  60. package/src/lib/compilation-error.ts +1 -0
  61. package/src/lib/files/compiler.ts +7 -3
  62. package/src/lib/hmr.ts +397 -0
  63. package/src/lib/injected-packages.ts +41 -11
  64. package/src/lib/loaders.ts +80 -0
  65. package/src/lib/module-store.ts +190 -9
  66. package/src/lib/scripts/compiler.ts +248 -24
  67. package/src/lib/scripts/conditions.ts +1 -0
  68. package/src/lib/scripts/emit.ts +50 -5
  69. package/src/lib/scripts/resolve.ts +124 -2
  70. package/src/lib/scripts/transform.ts +290 -19
  71. package/src/lib/styles/compiler.ts +108 -8
  72. package/src/lib/styles/emit.ts +1 -1
  73. package/src/lib/styles/resolve.ts +11 -7
  74. package/src/types/hmr.d.ts +36 -0
@@ -23,6 +23,7 @@ import type { ResolveArgs, ResolvedModule } from './resolve.ts'
23
23
  import type { CompiledRoutes } from '../routes.ts'
24
24
  import type { ResolvedScriptTarget } from '../target.ts'
25
25
  import { createModuleStore } from '../module-store.ts'
26
+ import type { ModuleLoader } from '../loaders.ts'
26
27
  import type {
27
28
  FileSnapshot,
28
29
  ModuleRecord,
@@ -65,9 +66,15 @@ type ScriptCompilerOptions = {
65
66
  define?: Record<string, string>
66
67
  external: string[]
67
68
  fingerprintAssets: boolean
69
+ hmr?: {
70
+ clientPathname: string
71
+ send(updates: ScriptHmrUpdate[]): void
72
+ }
68
73
  isAllowed(absolutePath: string): boolean
69
74
  minify: boolean
75
+ loaders: readonly ModuleLoader[]
70
76
  onWatchDirectoriesChange?: (delta: { add: string[]; remove: string[] }) => void
77
+ onWatchFilesChange?: (delta: { add: string[]; remove: string[] }) => void
71
78
  rootDir: string
72
79
  routes: CompiledRoutes
73
80
  sourceMapSourcePaths: 'absolute' | 'url'
@@ -81,7 +88,8 @@ type ScriptCompiler = {
81
88
  getScript(filePath: string, options: ScriptGetOptions): Promise<ScriptGetResult>
82
89
  getPreloadLayers(filePath: string | readonly string[]): Promise<string[][]>
83
90
  getHref(filePath: string): Promise<string>
84
- handleFileEvent(filePath: string, event: ModuleWatchEvent): Promise<void>
91
+ classifyHmrFileEvent(filePath: string, event: ModuleWatchEvent): Promise<ScriptHmrUpdate[]>
92
+ invalidateFileEvent(filePath: string, event: ModuleWatchEvent): void
85
93
  parseRequestPathname(pathname: string): ParsedRequestPathname | null
86
94
  }
87
95
 
@@ -92,6 +100,26 @@ type ParsedRequestPathname = {
92
100
  requestedFingerprint: string | null
93
101
  }
94
102
 
103
+ export type ScriptHmrUpdate =
104
+ | {
105
+ accepted: false
106
+ filePath: string
107
+ path: string
108
+ timestamp: number
109
+ }
110
+ | {
111
+ accepted: true
112
+ acceptedPath: string
113
+ filePath: string
114
+ path: string
115
+ timestamp: number
116
+ }
117
+
118
+ type ScriptHmrBoundary = {
119
+ acceptedModule: ResolvedModule
120
+ boundaryModule: ResolvedModule
121
+ }
122
+
95
123
  const supportedScriptExtensionSet = new Set<string>(supportedScriptExtensions)
96
124
  const preloadConcurrency = Math.max(1, Math.min(8, os.availableParallelism() - 1))
97
125
 
@@ -108,7 +136,14 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
108
136
  ResolvedModule,
109
137
  EmittedModule
110
138
  >({
139
+ getAcceptedDependencies(resolvedModule) {
140
+ return resolvedModule.hmr.acceptedDeps.map((acceptedDep) => acceptedDep.depPath)
141
+ },
142
+ getDependencies(resolvedModule) {
143
+ return resolvedModule.deps
144
+ },
111
145
  onWatchDirectoriesChange: options.onWatchDirectoriesChange,
146
+ onWatchFilesChange: options.onWatchFilesChange,
112
147
  })
113
148
  let tsconfigTransformOptionsResolver = createTsconfigTransformOptionsResolver()
114
149
  let resolverFactory = new ResolverFactory({
@@ -128,6 +163,7 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
128
163
  externalSet: resolvedOptions.externalSet,
129
164
  isWatchIgnored,
130
165
  minify: resolvedOptions.minify,
166
+ loaders: resolvedOptions.loaders,
131
167
  resolveActualPath,
132
168
  routes: resolvedOptions.routes,
133
169
  sourceMapSourcePaths: resolvedOptions.sourceMapSourcePaths,
@@ -202,26 +238,38 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
202
238
  return getServedUrl(resolvedModule.identityPath)
203
239
  },
204
240
 
205
- async handleFileEvent(filePath, event) {
241
+ async classifyHmrFileEvent(filePath, event) {
206
242
  let normalizedFilePath = normalizeFilePath(filePath)
207
- if (isWatchIgnored(normalizedFilePath)) return
208
-
209
- if (shouldClearResolverCacheForFileEvent(normalizedFilePath, event)) {
210
- resolverFactory.clearCache()
211
- }
212
-
213
- if (isTsconfigPath(normalizedFilePath)) {
214
- tsconfigTransformOptionsResolver.clear()
215
- scriptStore.invalidateAll()
216
- return
217
- }
218
-
219
- if (isPackageJsonPath(normalizedFilePath)) {
220
- scriptStore.invalidateAll()
221
- return
243
+ if (isWatchIgnored(normalizedFilePath)) return []
244
+
245
+ let timestamp = Date.now()
246
+ let previousResolvedModule = scriptStore.getLastResolved(normalizedFilePath)
247
+ let updatePathname = previousResolvedModule?.stableUrlPathname
248
+
249
+ invalidateScriptFileEvent(normalizedFilePath, event)
250
+
251
+ let resolvedModule =
252
+ event === 'change' && updatePathname
253
+ ? await tryGetOrCreateResolvedScript(scriptStore.get(normalizedFilePath))
254
+ : undefined
255
+ let hmrUpdate =
256
+ event === 'change' && updatePathname
257
+ ? getHmrUpdatesForChange(
258
+ resolvedModule ?? previousResolvedModule,
259
+ previousResolvedModule,
260
+ updatePathname,
261
+ timestamp,
262
+ )
263
+ : []
264
+
265
+ if (hmrUpdate.length > 0) {
266
+ resolvedOptions.hmr?.send(hmrUpdate)
222
267
  }
268
+ return hmrUpdate
269
+ },
223
270
 
224
- scriptStore.invalidateForFileEvent(normalizedFilePath, event)
271
+ invalidateFileEvent(filePath, event) {
272
+ invalidateScriptFileEvent(normalizeFilePath(filePath), event)
225
273
  },
226
274
 
227
275
  parseRequestPathname(pathname) {
@@ -252,6 +300,27 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
252
300
  return resolveFilePath(resolvedOptions.rootDir, filePath)
253
301
  }
254
302
 
303
+ function invalidateScriptFileEvent(normalizedFilePath: string, event: ModuleWatchEvent): void {
304
+ if (isWatchIgnored(normalizedFilePath)) return
305
+
306
+ if (shouldClearResolverCacheForFileEvent(normalizedFilePath, event)) {
307
+ resolverFactory.clearCache()
308
+ }
309
+
310
+ if (isTsconfigPath(normalizedFilePath)) {
311
+ tsconfigTransformOptionsResolver.clear()
312
+ scriptStore.invalidateAll()
313
+ return
314
+ }
315
+
316
+ if (isPackageJsonPath(normalizedFilePath)) {
317
+ scriptStore.invalidateAll()
318
+ return
319
+ }
320
+
321
+ scriptStore.invalidateForFileEvent(normalizedFilePath, event)
322
+ }
323
+
255
324
  function resolveServedScriptOrThrow(absolutePath: string): ResolveModuleResult {
256
325
  let resolvedModule = resolveModulePath(absolutePath)
257
326
  if (!resolvedModule) {
@@ -262,7 +331,8 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
262
331
 
263
332
  if (!resolvedOptions.isAllowed(resolvedModule.identityPath)) {
264
333
  throw createAssetServerCompilationError(
265
- `File is not allowed: ${resolvedModule.identityPath}`,
334
+ `File "${resolvedModule.identityPath}" is not allowed by the asset server access configuration. ` +
335
+ `Add a matching allowFiles or allowPackages rule, or remove a conflicting denyFiles rule.`,
266
336
  {
267
337
  code: 'FILE_NOT_ALLOWED',
268
338
  },
@@ -276,8 +346,14 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
276
346
  record: ScriptRecord,
277
347
  options: ScriptGetOptions,
278
348
  ): ScriptGetResult | null {
279
- let current = getNotModifiedResult(record.emitted, options)
280
- if (current) return current
349
+ if (hasHmrTimestampedDependency(record.resolved)) {
350
+ return null
351
+ }
352
+
353
+ if (scriptStore.isEmittedFresh(record)) {
354
+ let current = getNotModifiedResult(record.emitted, options)
355
+ if (current) return current
356
+ }
281
357
 
282
358
  if (!record.staleEmittedSnapshot || !isModuleSnapshotFresh(record.staleEmittedSnapshot)) {
283
359
  return null
@@ -293,7 +369,7 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
293
369
  }
294
370
 
295
371
  async function getOrCreateResolvedScript(record: ScriptRecord): Promise<ResolvedModule> {
296
- if (record.resolved) return record.resolved
372
+ if (record.resolved && scriptStore.isResolvedFresh(record)) return record.resolved
297
373
 
298
374
  let cacheKey = getRecordCacheKey(record)
299
375
  let existing = resolveInFlightByCacheKey.get(cacheKey)
@@ -339,8 +415,18 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
339
415
  }
340
416
  }
341
417
 
418
+ async function tryGetOrCreateResolvedScript(
419
+ record: ScriptRecord,
420
+ ): Promise<ResolvedModule | undefined> {
421
+ try {
422
+ return await getOrCreateResolvedScript(record)
423
+ } catch {
424
+ return undefined
425
+ }
426
+ }
427
+
342
428
  async function getOrCreateTransformedScript(record: ScriptRecord): Promise<TransformedModule> {
343
- if (record.transformed) return record.transformed
429
+ if (record.transformed && scriptStore.isTransformedFresh(record)) return record.transformed
344
430
 
345
431
  let startedVersion = record.invalidationVersion
346
432
  let transformModuleResult = await transformModule(record, transformArgs)
@@ -362,7 +448,13 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
362
448
  }
363
449
 
364
450
  async function getOrCreateEmittedScript(record: ScriptRecord): Promise<EmittedModule> {
365
- if (record.emitted) return record.emitted
451
+ if (
452
+ record.emitted &&
453
+ scriptStore.isEmittedFresh(record) &&
454
+ !hasHmrTimestampedDependency(record.resolved)
455
+ ) {
456
+ return record.emitted
457
+ }
366
458
 
367
459
  let cacheKey = getRecordCacheKey(record)
368
460
  let existing = emitInFlightByCacheKey.get(cacheKey)
@@ -373,6 +465,9 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
373
465
  let resolvedModule = await getOrCreateResolvedScript(record)
374
466
  let emitResolvedModuleResult = await emitResolvedModule(resolvedModule, {
375
467
  getServedUrl,
468
+ getStableUrl,
469
+ getHmrImportTimestamp,
470
+ hmrClientPathname: resolvedOptions.hmr?.clientPathname,
376
471
  sourceMaps: resolvedOptions.sourceMaps,
377
472
  })
378
473
 
@@ -415,11 +510,140 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
415
510
  )
416
511
  }
417
512
 
513
+ function getStableUrl(identityPath: string): string {
514
+ let stableUrlPathname = resolvedOptions.routes.toUrlPathname(identityPath)
515
+ if (!stableUrlPathname) {
516
+ throw createAssetServerCompilationError(
517
+ `File ${identityPath} is outside all configured fileMap entries.`,
518
+ {
519
+ code: 'FILE_OUTSIDE_FILE_MAP',
520
+ },
521
+ )
522
+ }
523
+ return stableUrlPathname
524
+ }
525
+
526
+ function getHmrImportTimestamp(identityPath: string): number | null {
527
+ return scriptStore.getHmrUpdateTimestamp(identityPath) ?? null
528
+ }
529
+
530
+ function hasHmrTimestampedDependency(resolvedModule: ResolvedModule | undefined): boolean {
531
+ return resolvedModule?.deps.some((depPath) => getHmrImportTimestamp(depPath) !== null) === true
532
+ }
533
+
534
+ function getHmrUpdatesForChange(
535
+ resolvedModule: ResolvedModule | undefined,
536
+ previousResolvedModule: ResolvedModule | undefined,
537
+ updatePathname: string,
538
+ timestamp: number,
539
+ ): ScriptHmrUpdate[] {
540
+ if (resolvedModule) {
541
+ scriptStore.setHmrUpdateTimestamp(resolvedModule.identityPath, timestamp)
542
+ }
543
+
544
+ if (resolvedModule?.hmr.selfAccepting === true) {
545
+ return [
546
+ {
547
+ accepted: true,
548
+ acceptedPath: updatePathname,
549
+ filePath: resolvedModule.identityPath,
550
+ path: updatePathname,
551
+ timestamp,
552
+ },
553
+ ]
554
+ }
555
+
556
+ let sourceFilePath = resolvedModule?.identityPath
557
+ let boundaries = findHmrBoundaries(sourceFilePath)
558
+ if (sourceFilePath !== undefined && boundaries) {
559
+ return dedupeHmrBoundaries(boundaries).map(({ acceptedModule, boundaryModule }) => ({
560
+ accepted: true,
561
+ acceptedPath: acceptedModule.stableUrlPathname,
562
+ filePath: sourceFilePath,
563
+ path: boundaryModule.stableUrlPathname,
564
+ timestamp,
565
+ }))
566
+ }
567
+
568
+ return [
569
+ {
570
+ accepted: false,
571
+ filePath:
572
+ resolvedModule?.identityPath ?? previousResolvedModule?.identityPath ?? updatePathname,
573
+ path: updatePathname,
574
+ timestamp,
575
+ },
576
+ ]
577
+ }
578
+
579
+ function findHmrBoundaries(identityPath: string | undefined): ScriptHmrBoundary[] | null {
580
+ if (identityPath === undefined) return null
581
+ return propagateHmrUpdate(identityPath, new Set())
582
+ }
583
+
584
+ function propagateHmrUpdate(
585
+ identityPath: string,
586
+ traversed: Set<string>,
587
+ ): ScriptHmrBoundary[] | null {
588
+ if (traversed.has(identityPath)) return []
589
+ traversed.add(identityPath)
590
+
591
+ let resolvedModule = scriptStore.getLastResolved(identityPath)
592
+ if (!resolvedModule) return null
593
+
594
+ if (resolvedModule.hmr.selfAccepting) {
595
+ return [
596
+ {
597
+ acceptedModule: resolvedModule,
598
+ boundaryModule: resolvedModule,
599
+ },
600
+ ]
601
+ }
602
+
603
+ let importerPaths = scriptStore.getImporters(identityPath)
604
+ if (!importerPaths || importerPaths.size === 0) return null
605
+
606
+ let acceptedImporterPaths = scriptStore.getAcceptedImporters(identityPath)
607
+ let boundaries: ScriptHmrBoundary[] = []
608
+ for (let importerPath of importerPaths) {
609
+ let importer = scriptStore.getLastResolved(importerPath)
610
+ if (!importer) return null
611
+
612
+ if (acceptedImporterPaths?.has(importerPath)) {
613
+ boundaries.push({
614
+ acceptedModule: resolvedModule,
615
+ boundaryModule: importer,
616
+ })
617
+ continue
618
+ }
619
+
620
+ let importerBoundaries = propagateHmrUpdate(importerPath, traversed)
621
+ if (!importerBoundaries) return null
622
+ boundaries.push(...importerBoundaries)
623
+ }
624
+
625
+ return boundaries
626
+ }
627
+
418
628
  function isWatchIgnored(filePath: string): boolean {
419
629
  return resolvedOptions.watchIgnoreMatchers.some((matcher) => matcher(filePath))
420
630
  }
421
631
  }
422
632
 
633
+ function dedupeHmrBoundaries(boundaries: ScriptHmrBoundary[]): ScriptHmrBoundary[] {
634
+ let seen = new Set<string>()
635
+ let result: ScriptHmrBoundary[] = []
636
+
637
+ for (let boundary of boundaries) {
638
+ let key = `${boundary.boundaryModule.identityPath}\0${boundary.acceptedModule.identityPath}`
639
+ if (seen.has(key)) continue
640
+ seen.add(key)
641
+ result.push(boundary)
642
+ }
643
+
644
+ return result
645
+ }
646
+
423
647
  function getRecordCacheKey(record: ScriptRecord): string {
424
648
  return `${record.identityPath}\0${record.invalidationVersion}`
425
649
  }
@@ -0,0 +1 @@
1
+ export const scriptLoaderConditions = ['browser', 'import', 'module', 'default'] as const
@@ -31,10 +31,19 @@ type EmitResult =
31
31
  error: AssetServerCompilationError
32
32
  }
33
33
 
34
+ type RewriteImportsOptions = {
35
+ getHmrImportTimestamp(identityPath: string): number | null
36
+ getServedUrl(identityPath: string): Promise<string>
37
+ getStableUrl(identityPath: string): string
38
+ }
39
+
34
40
  export async function emitResolvedModule(
35
41
  resolvedModule: ResolvedModule,
36
42
  options: {
43
+ getHmrImportTimestamp(identityPath: string): number | null
37
44
  getServedUrl(identityPath: string): Promise<string>
45
+ getStableUrl(identityPath: string): string
46
+ hmrClientPathname?: string
38
47
  sourceMaps?: 'external' | 'inline'
39
48
  },
40
49
  ): Promise<EmitResult> {
@@ -43,7 +52,7 @@ export async function emitResolvedModule(
43
52
  resolvedModule.deps.map((depPath) => options.getServedUrl(depPath)),
44
53
  )
45
54
  let rewriteResult = await rewriteImports(resolvedModule, options)
46
- let finalCode = rewriteResult.code
55
+ let finalCode = prependHmrContext(resolvedModule, rewriteResult.code, options)
47
56
 
48
57
  if (rewriteResult.sourceMap) {
49
58
  if (options.sourceMaps === 'inline') {
@@ -75,14 +84,16 @@ export async function emitResolvedModule(
75
84
 
76
85
  async function rewriteImports(
77
86
  resolvedModule: ResolvedModule,
78
- options: {
79
- getServedUrl(identityPath: string): Promise<string>
80
- },
87
+ options: RewriteImportsOptions,
81
88
  ): Promise<{ code: string; sourceMap: string | null }> {
82
89
  let rewrittenSource = new MagicString(resolvedModule.rawCode)
83
90
 
84
91
  for (let imported of resolvedModule.imports) {
85
92
  let url = await options.getServedUrl(imported.depPath)
93
+ let hmrImportTimestamp = options.getHmrImportTimestamp(imported.depPath)
94
+ if (hmrImportTimestamp !== null) {
95
+ url = addTimestampQuery(url, hmrImportTimestamp)
96
+ }
86
97
  rewrittenSource.overwrite(
87
98
  imported.start,
88
99
  imported.end,
@@ -90,9 +101,19 @@ async function rewriteImports(
90
101
  )
91
102
  }
92
103
 
104
+ for (let acceptedDep of resolvedModule.hmr.acceptedDeps) {
105
+ let url = options.getStableUrl(acceptedDep.depPath)
106
+ rewrittenSource.overwrite(
107
+ acceptedDep.start,
108
+ acceptedDep.end,
109
+ acceptedDep.quote ? `${acceptedDep.quote}${url}${acceptedDep.quote}` : url,
110
+ )
111
+ }
112
+
93
113
  let code = rewrittenSource.toString()
94
114
  let sourceMap =
95
- resolvedModule.sourceMap && resolvedModule.imports.length > 0
115
+ resolvedModule.sourceMap &&
116
+ (resolvedModule.imports.length > 0 || resolvedModule.hmr.acceptedDeps.length > 0)
96
117
  ? composeSourceMaps(
97
118
  rewrittenSource.generateMap({ hires: true }).toString(),
98
119
  resolvedModule.sourceMap,
@@ -102,6 +123,30 @@ async function rewriteImports(
102
123
  return { code, sourceMap }
103
124
  }
104
125
 
126
+ function addTimestampQuery(pathname: string, timestamp: number): string {
127
+ return `${pathname}${pathname.includes('?') ? '&' : '?'}t=${timestamp}`
128
+ }
129
+
130
+ function prependHmrContext(
131
+ resolvedModule: ResolvedModule,
132
+ code: string,
133
+ options: {
134
+ hmrClientPathname?: string
135
+ },
136
+ ): string {
137
+ if (!options.hmrClientPathname || !resolvedModule.hmr.usesImportMetaHot) return code
138
+
139
+ return (
140
+ `import { createHotContext as __remixCreateHotContext } from ${JSON.stringify(
141
+ options.hmrClientPathname,
142
+ )};\n` +
143
+ `import.meta.hot = __remixCreateHotContext(${JSON.stringify(
144
+ resolvedModule.stableUrlPathname,
145
+ )});\n` +
146
+ code
147
+ )
148
+ }
149
+
105
150
  async function createEmittedAsset(content: string): Promise<EmittedAsset> {
106
151
  return {
107
152
  content,
@@ -37,6 +37,8 @@ type ResolvedImport = {
37
37
  start: number
38
38
  }
39
39
 
40
+ type ResolvedHmrAcceptedDependency = ResolvedImport
41
+
40
42
  type RelativeImportResolution = {
41
43
  candidatePaths: readonly string[]
42
44
  candidatePrefixes: readonly string[]
@@ -50,6 +52,9 @@ type TrackedResolution = RelativeImportResolution & {
50
52
  export type ResolvedModule = {
51
53
  deps: string[]
52
54
  fingerprint: string | null
55
+ hmr: Omit<TransformedModule['hmr'], 'acceptedDeps'> & {
56
+ acceptedDeps: ResolvedHmrAcceptedDependency[]
57
+ }
53
58
  identityPath: string
54
59
  imports: ResolvedImport[]
55
60
  trackedFiles: string[]
@@ -116,6 +121,7 @@ export async function resolveModule(
116
121
  }
117
122
 
118
123
  let importsWithPaths: ResolvedImport[] = []
124
+ let acceptedDepsWithPaths: ResolvedHmrAcceptedDependency[] = []
119
125
  let deps = new Set<string>()
120
126
 
121
127
  for (let unresolved of transformed.unresolvedImports) {
@@ -163,8 +169,8 @@ export async function resolveModule(
163
169
  if (!args.isAllowed(resolvedImport.identityPath)) {
164
170
  return failResolve(
165
171
  createAssetServerCompilationError(
166
- `Import "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedImport.identityPath}", is not allowed by the asset server allow/deny configuration. ` +
167
- `Add a matching allow rule for this file path, remove a conflicting deny rule for this file path, or mark this import as external.`,
172
+ `Import "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedImport.identityPath}", is not allowed by the asset server access configuration. ` +
173
+ `Add a matching allowFiles or allowPackages rule, remove a conflicting denyFiles rule, or mark this import as external.`,
168
174
  {
169
175
  code: 'IMPORT_NOT_ALLOWED',
170
176
  },
@@ -218,12 +224,124 @@ export async function resolveModule(
218
224
  })
219
225
  }
220
226
 
227
+ for (let unresolved of transformed.hmr.acceptedDeps) {
228
+ if (isBrowserExternalModuleUrl(unresolved.specifier)) continue
229
+
230
+ let displaySpecifier = getDisplayImportSpecifier(unresolved.specifier)
231
+ let trackedResolution = getTrackedRelativeImportResolution(
232
+ transformed.importerDir,
233
+ displaySpecifier,
234
+ args.isWatchIgnored,
235
+ )
236
+
237
+ let resolvedSpec = resolvedImports.get(unresolved.specifier)
238
+ if (!resolvedSpec?.absolutePath) {
239
+ try {
240
+ resolvedSpec = await batchResolveSpecifiers(
241
+ [unresolved.specifier],
242
+ transformed.resolvedPath,
243
+ args.resolverFactory,
244
+ ).then((resolved) => resolved.get(unresolved.specifier))
245
+ } catch (error) {
246
+ return failResolve(error, trackedFiles, trackedResolutions, transformed.resolvedPath, {
247
+ isWatchIgnored: args.isWatchIgnored,
248
+ trackedResolution,
249
+ })
250
+ }
251
+ }
252
+
253
+ if (!resolvedSpec?.absolutePath) {
254
+ return failResolve(
255
+ createAssetServerCompilationError(
256
+ `Failed to resolve accepted HMR dependency "${displaySpecifier}" in ${transformed.resolvedPath}. ` +
257
+ `Ensure it resolves to a file within the configured asset server fileMap, or mark it as external.`,
258
+ {
259
+ code: 'IMPORT_RESOLUTION_FAILED',
260
+ },
261
+ ),
262
+ trackedFiles,
263
+ trackedResolutions,
264
+ transformed.resolvedPath,
265
+ { isWatchIgnored: args.isWatchIgnored, trackedResolution },
266
+ )
267
+ }
268
+
269
+ let resolvedImport = args.resolveModulePath(resolvedSpec.absolutePath)
270
+ if (!resolvedImport) {
271
+ return failResolve(
272
+ createAssetServerCompilationError(
273
+ `Accepted HMR dependency "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedSpec.absolutePath}", is not a supported script file. ` +
274
+ `Supported extensions are ${supportedScriptExtensions.join(', ')}.`,
275
+ {
276
+ code: 'IMPORT_NOT_SUPPORTED',
277
+ },
278
+ ),
279
+ trackedFiles,
280
+ trackedResolutions,
281
+ transformed.resolvedPath,
282
+ { isWatchIgnored: args.isWatchIgnored, trackedResolution },
283
+ )
284
+ }
285
+
286
+ if (!args.isAllowed(resolvedImport.identityPath)) {
287
+ return failResolve(
288
+ createAssetServerCompilationError(
289
+ `Accepted HMR dependency "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedImport.identityPath}", is not allowed by the asset server allow/deny configuration. ` +
290
+ `Add a matching allow rule for this file path, remove a conflicting deny rule for this file path, or mark this import as external.`,
291
+ {
292
+ code: 'IMPORT_NOT_ALLOWED',
293
+ },
294
+ ),
295
+ trackedFiles,
296
+ trackedResolutions,
297
+ transformed.resolvedPath,
298
+ { isWatchIgnored: args.isWatchIgnored, trackedResolution },
299
+ )
300
+ }
301
+
302
+ let stableUrlPathname = args.routes.toUrlPathname(resolvedImport.identityPath)
303
+ if (!stableUrlPathname) {
304
+ return failResolve(
305
+ createAssetServerCompilationError(
306
+ `Accepted HMR dependency "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedImport.identityPath}", is outside all configured fileMap entries. ` +
307
+ `Add a matching fileMap entry for this file path, or mark this import as external.`,
308
+ {
309
+ code: 'IMPORT_OUTSIDE_FILE_MAP',
310
+ },
311
+ ),
312
+ trackedFiles,
313
+ trackedResolutions,
314
+ transformed.resolvedPath,
315
+ { isWatchIgnored: args.isWatchIgnored, trackedResolution },
316
+ )
317
+ }
318
+
319
+ if (trackedResolution) {
320
+ trackedResolutions.push({
321
+ ...trackedResolution,
322
+ resolvedIdentityPath: resolvedImport.identityPath,
323
+ })
324
+ }
325
+
326
+ acceptedDepsWithPaths.push({
327
+ depPath: resolvedImport.identityPath,
328
+ end: unresolved.end,
329
+ quote: unresolved.quote,
330
+ start: unresolved.start,
331
+ })
332
+ }
333
+
221
334
  return {
222
335
  ok: true,
223
336
  tracking: toResolveTracking(trackedFiles, trackedResolutions),
224
337
  value: {
225
338
  deps: [...deps],
226
339
  fingerprint: transformed.fingerprint,
340
+ hmr: {
341
+ acceptedDeps: acceptedDepsWithPaths,
342
+ selfAccepting: transformed.hmr.selfAccepting,
343
+ usesImportMetaHot: transformed.hmr.usesImportMetaHot,
344
+ },
227
345
  identityPath: record.identityPath,
228
346
  imports: importsWithPaths,
229
347
  trackedFiles: [...trackedFiles],
@@ -416,6 +534,10 @@ function getDisplayImportSpecifier(specifier: string): string {
416
534
  return restoreAuthoredInjectedPackageSpecifier(specifier) ?? specifier
417
535
  }
418
536
 
537
+ function isBrowserExternalModuleUrl(url: string): boolean {
538
+ return url.startsWith('data:') || url.startsWith('http://') || url.startsWith('https://')
539
+ }
540
+
419
541
  function failResolve(
420
542
  error: unknown,
421
543
  trackedFiles: ReadonlySet<string>,