@remix-run/assets 0.6.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 (72) hide show
  1. package/README.md +101 -13
  2. package/dist/assets.d.ts +2 -1
  3. package/dist/assets.d.ts.map +1 -1
  4. package/dist/lib/asset-server.d.ts +41 -19
  5. package/dist/lib/asset-server.d.ts.map +1 -1
  6. package/dist/lib/asset-server.js +98 -41
  7. package/dist/lib/compilation-error.d.ts +1 -1
  8. package/dist/lib/compilation-error.d.ts.map +1 -1
  9. package/dist/lib/files/compiler.d.ts +1 -1
  10. package/dist/lib/files/compiler.d.ts.map +1 -1
  11. package/dist/lib/files/compiler.js +26 -29
  12. package/dist/lib/files/config.d.ts +6 -0
  13. package/dist/lib/files/config.d.ts.map +1 -1
  14. package/dist/lib/files/config.js +9 -0
  15. package/dist/lib/fingerprint.d.ts +0 -4
  16. package/dist/lib/fingerprint.d.ts.map +1 -1
  17. package/dist/lib/fingerprint.js +0 -4
  18. package/dist/lib/hmr.d.ts +7 -0
  19. package/dist/lib/hmr.d.ts.map +1 -1
  20. package/dist/lib/hmr.js +198 -25
  21. package/dist/lib/routes.d.ts +1 -0
  22. package/dist/lib/routes.d.ts.map +1 -1
  23. package/dist/lib/routes.js +1 -1
  24. package/dist/lib/scripts/compiler.d.ts +8 -2
  25. package/dist/lib/scripts/compiler.d.ts.map +1 -1
  26. package/dist/lib/scripts/compiler.js +186 -36
  27. package/dist/lib/scripts/emit.d.ts +2 -1
  28. package/dist/lib/scripts/emit.d.ts.map +1 -1
  29. package/dist/lib/scripts/emit.js +25 -16
  30. package/dist/lib/scripts/resolve.d.ts +7 -1
  31. package/dist/lib/scripts/resolve.d.ts.map +1 -1
  32. package/dist/lib/scripts/resolve.js +110 -3
  33. package/dist/lib/scripts/specifiers.d.ts +2 -0
  34. package/dist/lib/scripts/specifiers.d.ts.map +1 -0
  35. package/dist/lib/scripts/specifiers.js +9 -0
  36. package/dist/lib/scripts/transform.d.ts +2 -3
  37. package/dist/lib/scripts/transform.d.ts.map +1 -1
  38. package/dist/lib/scripts/transform.js +2 -16
  39. package/dist/lib/styles/compiler.d.ts +0 -1
  40. package/dist/lib/styles/compiler.d.ts.map +1 -1
  41. package/dist/lib/styles/compiler.js +105 -25
  42. package/dist/lib/styles/emit.d.ts +2 -1
  43. package/dist/lib/styles/emit.d.ts.map +1 -1
  44. package/dist/lib/styles/emit.js +12 -10
  45. package/dist/lib/styles/resolve.d.ts +0 -1
  46. package/dist/lib/styles/resolve.d.ts.map +1 -1
  47. package/dist/lib/styles/resolve.js +0 -1
  48. package/dist/lib/styles/transform.d.ts +0 -2
  49. package/dist/lib/styles/transform.d.ts.map +1 -1
  50. package/dist/lib/styles/transform.js +0 -7
  51. package/dist/lib/virtual-store.d.ts +8 -0
  52. package/dist/lib/virtual-store.d.ts.map +1 -0
  53. package/dist/lib/virtual-store.js +100 -0
  54. package/package.json +6 -5
  55. package/src/assets.ts +7 -1
  56. package/src/lib/asset-server.ts +175 -73
  57. package/src/lib/compilation-error.ts +1 -0
  58. package/src/lib/files/compiler.ts +30 -40
  59. package/src/lib/files/config.ts +17 -0
  60. package/src/lib/fingerprint.ts +0 -9
  61. package/src/lib/hmr.ts +210 -26
  62. package/src/lib/routes.ts +1 -1
  63. package/src/lib/scripts/compiler.ts +238 -52
  64. package/src/lib/scripts/emit.ts +34 -19
  65. package/src/lib/scripts/resolve.ts +166 -4
  66. package/src/lib/scripts/specifiers.ts +11 -0
  67. package/src/lib/scripts/transform.ts +4 -23
  68. package/src/lib/styles/compiler.ts +137 -39
  69. package/src/lib/styles/emit.ts +18 -13
  70. package/src/lib/styles/resolve.ts +0 -2
  71. package/src/lib/styles/transform.ts +0 -10
  72. package/src/lib/virtual-store.ts +124 -0
@@ -23,7 +23,7 @@ import { normalizeFilePath, normalizePathname } from './paths.ts'
23
23
  import { compileRoutes } from './routes.ts'
24
24
  import type { CompiledRoutes } from './routes.ts'
25
25
  import { createResponseForScript, createScriptCompiler } from './scripts/compiler.ts'
26
- import type { ScriptHmrUpdate } from './scripts/compiler.ts'
26
+ import type { ScriptHmrUpdate, ScriptImportMap } from './scripts/compiler.ts'
27
27
  import { supportedScriptExtensions } from './scripts/resolve.ts'
28
28
  import { createResponseForStyle, createStyleCompiler, isStyleFilePath } from './styles/compiler.ts'
29
29
  import { resolveScriptTarget, resolveStyleTarget } from './target.ts'
@@ -31,6 +31,7 @@ import type { AssetTarget, ResolvedScriptTarget, ResolvedStyleTarget } from './t
31
31
  import { createAssetServerWatcher } from './watch.ts'
32
32
  import { createAssetInspector, type AssetDetails } from './inspection.ts'
33
33
  import type { AssetServerWatcher, ChokidarWatcher } from './watch.ts'
34
+ import { getVirtualStoreMountConfigs } from './virtual-store.ts'
34
35
 
35
36
  interface AssetServerWatchOptions {
36
37
  /**
@@ -90,6 +91,18 @@ export type BrowserHmrChannelFactory = () =>
90
91
  | undefined
91
92
  | Promise<BrowserHmrChannel | undefined>
92
93
 
94
+ /** Browser HMR integration options. */
95
+ export interface BrowserHmrOptions {
96
+ /** Creates the channel that delivers browser HMR events. */
97
+ channel: BrowserHmrChannelFactory
98
+ /**
99
+ * Module specifier resolved relative to the asset server's root directory. It must point to a
100
+ * browser module exporting `importModule(specifier, parentUrl)` for evaluating JavaScript updates.
101
+ * The module and its dependencies must be allowed and reachable through configured mounts.
102
+ */
103
+ moduleImporter?: string
104
+ }
105
+
93
106
  /**
94
107
  * Converts a watcher batch into ordered browser update or reload events.
95
108
  *
@@ -125,14 +138,12 @@ export type BrowserHmrFileEvent = {
125
138
  */
126
139
  export type BrowserHmrEvent =
127
140
  | {
141
+ /** Consumer-owned data keyed by a stable, versioned namespace. */
142
+ data: Record<string, BrowserHmrData>
128
143
  /** Absolute source file paths that triggered this update. */
129
144
  files?: string[]
130
- /** Update time used to bypass browser module and stylesheet caches. */
131
- timestamp: number
132
145
  /** Browser update event. */
133
146
  type: 'update'
134
- /** Accepted JavaScript and CSS module updates for the browser to apply in place. */
135
- updates: Extract<HmrPayload, { type: 'browser:update' }>['updates']
136
147
  }
137
148
  | {
138
149
  /** Absolute source file paths that could not be handled in place. */
@@ -141,16 +152,15 @@ export type BrowserHmrEvent =
141
152
  type: 'reload'
142
153
  }
143
154
 
144
- interface FingerprintOptions {
145
- /**
146
- * Per-build invalidation token that must change whenever fingerprinted asset URLs
147
- * should be invalidated together.
148
- */
149
- buildId: string
150
- }
151
-
152
155
  type AssetSourceMaps = 'inline' | 'external'
153
156
  type AssetSourceMapSourcePaths = 'url' | 'absolute'
157
+ type BrowserHmrData =
158
+ | null
159
+ | boolean
160
+ | number
161
+ | string
162
+ | BrowserHmrData[]
163
+ | { [key: string]: BrowserHmrData }
154
164
 
155
165
  interface AssetServerScriptOptions {
156
166
  /**
@@ -158,7 +168,7 @@ interface AssetServerScriptOptions {
158
168
  * `{ 'process.env.NODE_ENV': '"production"' }`
159
169
  */
160
170
  define?: Record<string, string>
161
- /** Import specifiers to leave unrewritten (CDN URLs, import map entries, etc.) */
171
+ /** Import specifiers to treat as external dependencies (CDN URLs, import map entries, etc.) */
162
172
  external?: string[]
163
173
  /**
164
174
  * Synchronous loaders that post-process compiled JavaScript.
@@ -210,12 +220,12 @@ export interface AssetServerOptions<transforms extends AssetRequestTransformMap
210
220
  */
211
221
  denyFiles?: readonly string[]
212
222
  /**
213
- * Controls optional source-based URL fingerprinting for rewritten asset URLs.
223
+ * Controls optional content-based URL fingerprinting for served asset URLs.
214
224
  *
215
225
  * When omitted, all served assets use stable non-fingerprinted URLs with `Cache-Control: no-cache`.
216
226
  * Cannot be used together with active watch mode. Set `watch: false` when fingerprinting.
217
227
  */
218
- fingerprint?: FingerprintOptions
228
+ fingerprint?: boolean
219
229
  /**
220
230
  * Shared compatibility target for scripts and styles. Browser targets apply to both
221
231
  * pipelines, and `es` only affects scripts.
@@ -253,7 +263,7 @@ export interface AssetServerOptions<transforms extends AssetRequestTransformMap
253
263
  * HMR requires `watch` to be enabled. The factory is called once for this asset server. Returning
254
264
  * `undefined` leaves HMR inactive; a returned channel is closed by `assetServer.close()`.
255
265
  */
256
- hmr?: BrowserHmrChannelFactory
266
+ hmr?: BrowserHmrChannelFactory | BrowserHmrOptions
257
267
  /**
258
268
  * Enable filesystem-backed cache invalidation for long-lived server instances.
259
269
  * Enabled by default. Pass `true` to use the default watcher options, an options
@@ -282,6 +292,18 @@ export type AssetServerGetHrefOptions<transforms extends AssetRequestTransformMa
282
292
  transform: readonly AssetTransformInvocation<transforms>[]
283
293
  }
284
294
 
295
+ /**
296
+ * Metadata needed to render or load a script entry module.
297
+ */
298
+ export interface ScriptEntry {
299
+ /** Public URL for the script entry module. */
300
+ href: string
301
+ /** Public URLs that should be emitted as `modulepreload` hints for this script graph. */
302
+ preloads: string[]
303
+ /** Import map entries required to resolve this script graph in the browser. */
304
+ importMap: ScriptImportMap
305
+ }
306
+
285
307
  /**
286
308
  * Serves compiled scripts and styles for asset requests routed to it.
287
309
  * Construct with {@link createAssetServer}.
@@ -296,10 +318,18 @@ export interface AssetServer<transforms extends AssetRequestTransformMap = {}> {
296
318
  * Returns the request href for a served asset file.
297
319
  */
298
320
  getHref(filePath: string, options?: AssetServerGetHrefOptions<transforms>): Promise<string>
321
+ /**
322
+ * Returns the href, preload URLs, and import map for a script entry module.
323
+ */
324
+ getScriptEntry(filePath: string): Promise<ScriptEntry>
299
325
  /**
300
326
  * Returns preload URLs for one or more served asset files, ordered shallowest-first.
301
327
  */
302
328
  getPreloads(filePath: string | readonly string[]): Promise<string[]>
329
+ /**
330
+ * Returns an import map for one or more script entry modules.
331
+ */
332
+ getImportMap(filePath: string | readonly string[]): Promise<ScriptImportMap>
303
333
  /**
304
334
  * Returns diagnostic details about one public asset URL or file path, including the matched mount
305
335
  * roots, access rules, file type, and browser-reachability status.
@@ -322,13 +352,13 @@ type ResolvedAssetServerOptions<transforms extends AssetRequestTransformMap> = {
322
352
  allowFiles: readonly string[]
323
353
  allowPackages?: readonly string[]
324
354
  basePath: string
325
- buildId?: string
326
355
  define?: Record<string, string>
327
356
  denyFiles?: readonly string[]
328
357
  external: string[]
329
358
  files: ResolvedAssetServerFilesOptions
330
359
  fingerprintAssets: boolean
331
360
  hmr: BrowserHmrChannelFactory | null
361
+ hmrModuleImporter: string | null
332
362
  minify: boolean
333
363
  loaders: readonly ModuleLoader[]
334
364
  onError: NonNullable<AssetServerOptions['onError']>
@@ -361,7 +391,7 @@ export function getInternalWatchTargets<transforms extends AssetRequestTransform
361
391
  * Create an asset server instance
362
392
  *
363
393
  * Compiles TypeScript/JavaScript scripts and CSS styles on demand with optional
364
- * source-based URL fingerprinting, caching, and configurable directory mounts.
394
+ * content-based URL fingerprinting, caching, and configurable directory mounts.
365
395
  *
366
396
  * @param options Server configuration
367
397
  * @returns A {@link AssetServer} with `fetch()`, `getHref()`, and `getPreloads()` methods
@@ -418,8 +448,8 @@ export function createAssetServer<const transforms extends AssetRequestTransform
418
448
  })
419
449
  let sendHmrPayload = resolvedOptions.hmr ? createHmrPayloadSender() : null
420
450
  let hmrPathnames = getHmrPathnames(resolvedOptions.basePath)
451
+ let hmrDataKey = `remix/assets@1:${resolvedOptions.basePath}`
421
452
  let scriptCompiler = createScriptCompiler({
422
- buildId: resolvedOptions.buildId,
423
453
  define: resolvedOptions.define,
424
454
  external: resolvedOptions.external,
425
455
  fingerprintAssets: resolvedOptions.fingerprintAssets,
@@ -451,7 +481,6 @@ export function createAssetServer<const transforms extends AssetRequestTransform
451
481
  watchMode: resolvedOptions.watchOptions !== null,
452
482
  })
453
483
  let styleCompiler = createStyleCompiler({
454
- buildId: resolvedOptions.buildId,
455
484
  fingerprintAssets: resolvedOptions.fingerprintAssets,
456
485
  getServedFileUrl: (identityPath: string, options: { transform: readonly string[] | null }) => {
457
486
  if (!fileCompiler) {
@@ -503,8 +532,8 @@ export function createAssetServer<const transforms extends AssetRequestTransform
503
532
  })
504
533
  if (resolvedOptions.files.extensions.length > 0) {
505
534
  fileCompiler = createFileCompiler({
506
- buildId: resolvedOptions.buildId,
507
535
  cache: resolvedOptions.files.cache,
536
+ cacheKey: resolvedOptions.files.cacheKey,
508
537
  extensions: resolvedOptions.files.extensions,
509
538
  fingerprintAssets: resolvedOptions.fingerprintAssets,
510
539
  globalTransforms: resolvedOptions.files.globalTransforms,
@@ -558,10 +587,10 @@ export function createAssetServer<const transforms extends AssetRequestTransform
558
587
  for (let event of events) {
559
588
  let normalizedFilePath = normalizeFilePath(event.filePath)
560
589
  let scriptUpdates = await scriptCompiler.classifyHmrFileEvent(normalizedFilePath, event.event)
561
- let scriptPayload = createScriptHmrPayload(scriptUpdates)
590
+ let scriptPayload = await createScriptHmrPayloadWithImportMaps(scriptUpdates)
562
591
  if (scriptPayload) {
563
592
  browserHmrEvents.push(
564
- createBrowserHmrEvent(scriptPayload, getScriptHmrUpdateFiles(scriptUpdates)),
593
+ createBrowserHmrEvent(scriptPayload, getScriptHmrUpdateFiles(scriptUpdates), hmrDataKey),
565
594
  )
566
595
  }
567
596
 
@@ -580,6 +609,7 @@ export function createAssetServer<const transforms extends AssetRequestTransform
580
609
  ],
581
610
  },
582
611
  [styleUpdate.filePath],
612
+ hmrDataKey,
583
613
  ),
584
614
  )
585
615
  }
@@ -588,6 +618,32 @@ export function createAssetServer<const transforms extends AssetRequestTransform
588
618
  }
589
619
 
590
620
  return browserHmrEvents
621
+
622
+ async function createScriptHmrPayloadWithImportMaps(
623
+ updates: ScriptHmrUpdate[],
624
+ ): Promise<Extract<HmrPayload, { type: 'browser:reload' | 'browser:update' }> | null> {
625
+ let payload = createScriptHmrPayload(updates)
626
+ if (!payload || payload.type === 'browser:reload') return payload
627
+
628
+ let acceptedUpdates = updates.filter((update) => update.accepted)
629
+ let importMaps = await Promise.all(
630
+ acceptedUpdates.map(async (update) => {
631
+ try {
632
+ return await scriptCompiler.getImportMap(update.acceptedFilePath)
633
+ } catch (error) {
634
+ if (!isAssetServerCompilationError(error)) throw error
635
+ return undefined
636
+ }
637
+ }),
638
+ )
639
+ return {
640
+ ...payload,
641
+ updates: payload.updates.map((update, index) => ({
642
+ ...update,
643
+ ...(importMaps[index] ? { importMap: importMaps[index] } : null),
644
+ })),
645
+ }
646
+ }
591
647
  }
592
648
 
593
649
  function updateBrowserHmrWatchedFiles(delta: BrowserHmrWatchedFileDelta): void {
@@ -615,7 +671,13 @@ export function createAssetServer<const transforms extends AssetRequestTransform
615
671
  if (requestPathname === hmrPathnames.client) {
616
672
  let browserHmrChannel = await browserHmrChannelPromise
617
673
  assertBrowserEventUrl(browserHmrChannel?.url)
618
- return createHmrClientResponse(browserHmrChannel.url, request.method)
674
+ return await createHmrClientResponse(
675
+ browserHmrChannel.url,
676
+ hmrDataKey,
677
+ resolvedOptions.hmrModuleImporter,
678
+ request.method,
679
+ scriptCompiler,
680
+ )
619
681
  }
620
682
  }
621
683
 
@@ -651,8 +713,10 @@ export function createAssetServer<const transforms extends AssetRequestTransform
651
713
  let compiledStyle = styleResult.style
652
714
 
653
715
  if (parsedRequestPathname.requestedFingerprint !== null) {
654
- if (compiledStyle.fingerprint !== parsedRequestPathname.requestedFingerprint)
655
- return null
716
+ let fingerprint = parsedRequestPathname.isSourceMapRequest
717
+ ? compiledStyle.sourceMap?.fingerprint
718
+ : compiledStyle.fingerprint
719
+ if (fingerprint !== parsedRequestPathname.requestedFingerprint) return null
656
720
  }
657
721
 
658
722
  return createResponseForStyle(compiledStyle, {
@@ -716,7 +780,10 @@ export function createAssetServer<const transforms extends AssetRequestTransform
716
780
  let compiledScript = scriptResult.script
717
781
 
718
782
  if (parsedRequestPathname.requestedFingerprint !== null) {
719
- if (compiledScript.fingerprint !== parsedRequestPathname.requestedFingerprint) return null
783
+ let fingerprint = parsedRequestPathname.isSourceMapRequest
784
+ ? compiledScript.sourceMap?.fingerprint
785
+ : compiledScript.fingerprint
786
+ if (fingerprint !== parsedRequestPathname.requestedFingerprint) return null
720
787
  }
721
788
 
722
789
  return createResponseForScript(compiledScript, {
@@ -779,6 +846,15 @@ export function createAssetServer<const transforms extends AssetRequestTransform
779
846
 
780
847
  return scriptCompiler.getHref(filePath)
781
848
  },
849
+ async getScriptEntry(filePath) {
850
+ let [href, preloads, importMap] = await Promise.all([
851
+ assetServer.getHref(filePath),
852
+ assetServer.getPreloads(filePath),
853
+ assetServer.getImportMap(filePath),
854
+ ])
855
+
856
+ return { href, preloads, importMap }
857
+ },
782
858
  async getPreloads(filePath) {
783
859
  let filePaths = Array.isArray(filePath) ? filePath : [filePath]
784
860
  let fileAssetFiles: string[] = []
@@ -843,6 +919,19 @@ export function createAssetServer<const transforms extends AssetRequestTransform
843
919
 
844
920
  return mergePreloadLayers(await Promise.all(preloadLayerGroupPromises))
845
921
  },
922
+ async getImportMap(filePath) {
923
+ let filePaths = Array.isArray(filePath) ? filePath : [filePath]
924
+ for (let nextFilePath of filePaths) {
925
+ let typeCheckFilePath = stripFilePathUrlSuffix(nextFilePath)
926
+ if (!isScriptFilePath(typeCheckFilePath)) {
927
+ throw new TypeError(
928
+ `assetServer.getImportMap() only supports script files: ${nextFilePath}`,
929
+ )
930
+ }
931
+ }
932
+
933
+ return scriptCompiler.getImportMap(filePath)
934
+ },
846
935
  async close() {
847
936
  if (closed) return
848
937
  closed = true
@@ -908,13 +997,27 @@ function getHmrPathnames(basePath: string): { client: string; events: string } {
908
997
  }
909
998
  }
910
999
 
911
- function createHmrClientResponse(eventPathname: string, method: string): Response {
912
- return new Response(method === 'HEAD' ? null : createHmrClientSource({ eventPathname }), {
913
- headers: {
914
- 'Cache-Control': 'no-cache',
915
- 'Content-Type': 'application/javascript; charset=utf-8',
1000
+ async function createHmrClientResponse(
1001
+ eventPathname: string,
1002
+ dataKey: string,
1003
+ moduleImporter: string | null,
1004
+ method: string,
1005
+ scriptCompiler: ReturnType<typeof createScriptCompiler>,
1006
+ ): Promise<Response> {
1007
+ let moduleImporterHref = moduleImporter
1008
+ ? await scriptCompiler.resolveSpecifierFromRoot(moduleImporter)
1009
+ : null
1010
+ return new Response(
1011
+ method === 'HEAD'
1012
+ ? null
1013
+ : createHmrClientSource({ dataKey, eventPathname, moduleImporter: moduleImporterHref }),
1014
+ {
1015
+ headers: {
1016
+ 'Cache-Control': 'no-cache',
1017
+ 'Content-Type': 'application/javascript; charset=utf-8',
1018
+ },
916
1019
  },
917
- })
1020
+ )
918
1021
  }
919
1022
 
920
1023
  function assertBrowserEventUrl(url: string | undefined): asserts url is string {
@@ -930,6 +1033,7 @@ function createHmrPayloadSender(): (payload: HmrPayload) => void {
930
1033
  function createBrowserHmrEvent(
931
1034
  payload: Extract<HmrPayload, { type: 'browser:reload' | 'browser:update' }>,
932
1035
  files: readonly string[],
1036
+ dataKey: string,
933
1037
  ): BrowserHmrEvent {
934
1038
  if (payload.type === 'browser:reload') {
935
1039
  return {
@@ -939,10 +1043,14 @@ function createBrowserHmrEvent(
939
1043
  }
940
1044
 
941
1045
  return {
1046
+ data: {
1047
+ [dataKey]: {
1048
+ timestamp: payload.timestamp,
1049
+ updates: payload.updates,
1050
+ },
1051
+ },
942
1052
  ...(files.length === 0 ? {} : { files: [...files] }),
943
- timestamp: payload.timestamp,
944
1053
  type: 'update',
945
- updates: payload.updates,
946
1054
  }
947
1055
  }
948
1056
 
@@ -968,7 +1076,9 @@ export function createScriptHmrPayload(
968
1076
  timestamp,
969
1077
  type: 'browser:update',
970
1078
  updates: acceptedUpdates.map((update) => ({
971
- ...(update.acceptedPath === update.path ? {} : { acceptedPath: update.acceptedPath }),
1079
+ ...(update.acceptedUrlPathname === update.path
1080
+ ? {}
1081
+ : { acceptedPath: update.acceptedUrlPathname }),
972
1082
  path: update.path,
973
1083
  type: 'js',
974
1084
  })),
@@ -1032,15 +1142,15 @@ function resolveAssetServerOptions<transforms extends AssetRequestTransformMap>(
1032
1142
  let rootDir = normalizeFilePath(fs.realpathSync(path.resolve(options.rootDir ?? process.cwd())))
1033
1143
  let basePath = normalizeBasePath(options.basePath)
1034
1144
  let scriptOptions = options.scripts ?? {}
1035
- let fingerprintOptions = normalizeFingerprintOptions({
1145
+ let fingerprintAssets = normalizeFingerprintOptions({
1036
1146
  fingerprint: options.fingerprint,
1037
1147
  watch: options.watch,
1038
1148
  })
1039
1149
  let watchOptions = normalizeWatchOptions(options.watch)
1040
- let hmrFactory = normalizeHmrFactory(options.hmr)
1150
+ let hmr = normalizeHmrOptions(options.hmr)
1041
1151
  let mounts = options.mounts ?? defaultMounts
1042
1152
 
1043
- if (hmrFactory && watchOptions === null) {
1153
+ if (hmr.channel && watchOptions === null) {
1044
1154
  throw new TypeError('hmr requires watch mode')
1045
1155
  }
1046
1156
  if (Object.keys(mounts).length === 0) {
@@ -1050,13 +1160,13 @@ function resolveAssetServerOptions<transforms extends AssetRequestTransformMap>(
1050
1160
  allowFiles: options.allowFiles,
1051
1161
  allowPackages: options.allowPackages,
1052
1162
  basePath,
1053
- buildId: fingerprintOptions.buildId,
1054
1163
  define: scriptOptions.define,
1055
1164
  denyFiles: options.denyFiles,
1056
1165
  external: scriptOptions.external ?? [],
1057
1166
  files: normalizeFilesOptions(options.files),
1058
- fingerprintAssets: fingerprintOptions.enabled,
1059
- hmr: hmrFactory,
1167
+ fingerprintAssets,
1168
+ hmr: hmr.channel,
1169
+ hmrModuleImporter: hmr.moduleImporter,
1060
1170
  minify: options.minify ?? false,
1061
1171
  mounts,
1062
1172
  loaders: scriptOptions.loaders ?? [],
@@ -1068,6 +1178,7 @@ function resolveAssetServerOptions<transforms extends AssetRequestTransformMap>(
1068
1178
  rootDir,
1069
1179
  },
1070
1180
  ...getInjectedPackageMountConfigs(),
1181
+ ...getVirtualStoreMountConfigs({ mounts, rootDir }),
1071
1182
  ]),
1072
1183
  sourceMapSourcePaths: options.sourceMapSourcePaths ?? 'url',
1073
1184
  sourceMaps: options.sourceMaps,
@@ -1077,14 +1188,22 @@ function resolveAssetServerOptions<transforms extends AssetRequestTransformMap>(
1077
1188
  }
1078
1189
  }
1079
1190
 
1080
- function normalizeHmrFactory(factory: AssetServerOptions['hmr']): BrowserHmrChannelFactory | null {
1081
- if (factory === undefined) return null
1082
-
1083
- if (typeof factory !== 'function') {
1084
- throw new TypeError('hmr must be a function')
1191
+ function normalizeHmrOptions(options: AssetServerOptions['hmr']): {
1192
+ channel: BrowserHmrChannelFactory | null
1193
+ moduleImporter: string | null
1194
+ } {
1195
+ if (options === undefined) return { channel: null, moduleImporter: null }
1196
+ if (typeof options === 'function') return { channel: options, moduleImporter: null }
1197
+ if (!options || typeof options !== 'object' || typeof options.channel !== 'function') {
1198
+ throw new TypeError('hmr must be a function or an object with a channel function')
1085
1199
  }
1086
-
1087
- return factory
1200
+ if (
1201
+ options.moduleImporter !== undefined &&
1202
+ (typeof options.moduleImporter !== 'string' || options.moduleImporter.trim().length === 0)
1203
+ ) {
1204
+ throw new TypeError('hmr.moduleImporter must be a non-empty string')
1205
+ }
1206
+ return { channel: options.channel, moduleImporter: options.moduleImporter ?? null }
1088
1207
  }
1089
1208
 
1090
1209
  function createBrowserHmrChannel(
@@ -1145,37 +1264,20 @@ function normalizeBasePath(basePath: string): string {
1145
1264
  function normalizeFingerprintOptions(options: {
1146
1265
  fingerprint: AssetServerOptions['fingerprint']
1147
1266
  watch: AssetServerOptions['watch']
1148
- }):
1149
- | {
1150
- enabled: false
1151
- buildId?: string
1152
- }
1153
- | {
1154
- enabled: true
1155
- buildId: string
1156
- } {
1157
- if (!options.fingerprint) {
1158
- return {
1159
- enabled: false,
1160
- }
1161
- }
1162
-
1163
- if (typeof options.fingerprint.buildId !== 'string') {
1164
- throw new TypeError('fingerprint.buildId must be a string')
1267
+ }): boolean {
1268
+ if (options.fingerprint !== undefined && typeof options.fingerprint !== 'boolean') {
1269
+ throw new TypeError('fingerprint must be a boolean')
1165
1270
  }
1166
1271
 
1167
- if (options.fingerprint.buildId.length === 0) {
1168
- throw new TypeError('fingerprint.buildId must be a non-empty string')
1272
+ if (!options.fingerprint) {
1273
+ return false
1169
1274
  }
1170
1275
 
1171
1276
  if (options.watch !== false) {
1172
1277
  throw new TypeError('fingerprint cannot be used with watch mode')
1173
1278
  }
1174
1279
 
1175
- return {
1176
- enabled: true,
1177
- buildId: options.fingerprint.buildId,
1178
- }
1280
+ return true
1179
1281
  }
1180
1282
 
1181
1283
  function normalizeWatchOptions(
@@ -15,6 +15,7 @@ type AssetServerCompilationErrorCode =
15
15
  | 'IMPORT_NOT_SUPPORTED'
16
16
  | 'IMPORT_NOT_ALLOWED'
17
17
  | 'IMPORT_OUTSIDE_MOUNTS'
18
+ | 'IMPORT_RESOLUTION_NOT_DIRECTORY_UNIFORM'
18
19
  | 'URL_RESOLUTION_FAILED'
19
20
  | 'URL_NOT_SUPPORTED'
20
21
  | 'URL_NOT_ALLOWED'