@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
package/README.md CHANGED
@@ -6,11 +6,11 @@ Fetch-based server for compiling browser assets on demand.
6
6
 
7
7
  - **On-Demand Compilation** - Compile browser scripts and styles on demand
8
8
  - **File Serving** - Serve configured file assets like images and fonts with optional transforms
9
- - **Custom File Mapping** - Define patterns for mapping public URLs to file paths on disk
10
9
  - **Access Control** - Control exactly which files and packages can be served
11
10
  - **Preloads** - Generate preload URLs for scripts and styles based on imports
11
+ - **Inspection** - List browser-reachable assets and explain URL-to-file mappings
12
12
  - **Caching** - Conservative caching by default with stable URLs, ETags, and revalidation
13
- - **Optional Fingerprinting** - Source-based fingerprinted URLs for long-lived browser caching
13
+ - **Optional Fingerprinting** - Content-based fingerprinted URLs for long-lived browser caching
14
14
  - **Source Maps** - Serve inline or external sourcemaps
15
15
  - **Hot Module Reloading** - Handle live code updates in development
16
16
  - **Script Loaders** - Post-process compiled JavaScript with Node-compatible loaders
@@ -21,6 +21,12 @@ Fetch-based server for compiling browser assets on demand.
21
21
  npm i remix
22
22
  ```
23
23
 
24
+ The optional image transform examples also use Sharp:
25
+
26
+ ```sh
27
+ npm i sharp
28
+ ```
29
+
24
30
  ## Usage
25
31
 
26
32
  Use `createAssetServer` to serve browser assets from a URL namespace in your app.
@@ -31,10 +37,6 @@ import { createAssetServer } from 'remix/assets'
31
37
 
32
38
  let assetServer = createAssetServer({
33
39
  basePath: '/assets',
34
- fileMap: {
35
- '/app/*path': 'app/*path',
36
- '/npm/*path': 'node_modules/*path',
37
- },
38
40
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
39
41
  allowPackages: ['remix'],
40
42
  files: {
@@ -51,6 +53,64 @@ router.get('/assets/*', ({ request }) => {
51
53
 
52
54
  This example gives you an `/assets/*` endpoint that serves compiled browser source from `public/` directories throughout `app/` and from the `remix` package.
53
55
 
56
+ ## Shared Configuration
57
+
58
+ Keep JSON-compatible asset mapping, access, and file-type settings in `remix.json` so the running
59
+ server and Remix CLI use the same configuration:
60
+
61
+ ```jsonc
62
+ {
63
+ "$schema": "./node_modules/remix/schema/remix.json",
64
+ "assets": {
65
+ "basePath": "/assets",
66
+ "mounts": {
67
+ "app": "app",
68
+ "npm": "node_modules",
69
+ },
70
+ "allowFiles": ["app/routes.ts", "app/**/public/**"],
71
+ "allowPackages": ["remix"],
72
+ "denyFiles": ["app/**/*.test.*"],
73
+ "files": {
74
+ "extensions": [".svg", ".png", ".jpg", ".woff2"],
75
+ },
76
+ },
77
+ }
78
+ ```
79
+
80
+ Load it from application code and add runtime-only behavior there:
81
+
82
+ ```ts
83
+ import { createAssetServer, defineFileTransform } from 'remix/assets'
84
+ import { loadConfig } from 'remix/cli'
85
+ import sharp from 'sharp'
86
+
87
+ let config = await loadConfig(import.meta.dirname)
88
+ if (config.assets === undefined) throw new Error('Missing assets configuration')
89
+ if (config.assets.files === undefined) throw new Error('Missing asset file configuration')
90
+
91
+ let assetServer = createAssetServer({
92
+ ...config.assets,
93
+ files: {
94
+ ...config.assets.files,
95
+ transforms: {
96
+ webp: defineFileTransform({
97
+ extensions: ['.png', '.jpg'],
98
+ async transform(bytes) {
99
+ return {
100
+ content: await sharp(bytes).webp({ quality: 80 }).toBuffer(),
101
+ extension: '.webp',
102
+ }
103
+ },
104
+ }),
105
+ },
106
+ },
107
+ })
108
+ ```
109
+
110
+ `loadConfig()` accepts either a config file or a directory. When given a directory, it searches
111
+ upward for the nearest `remix.json`. Run `remix assets` to list reachable files, or
112
+ `remix assets inspect <url-or-file>` to inspect one mapping and its access decision.
113
+
54
114
  ## Root Directory
55
115
 
56
116
  Use `rootDir` to specify the root directory of the asset server, which is used to resolve relative file paths. Defaults to `process.cwd()`.
@@ -62,10 +122,6 @@ import { createAssetServer } from 'remix/assets'
62
122
  let assetServer = createAssetServer({
63
123
  rootDir: path.resolve(import.meta.dirname, '..'),
64
124
  basePath: '/assets',
65
- fileMap: {
66
- '/app/*path': 'app/*path',
67
- '/npm/*path': 'node_modules/*path',
68
- },
69
125
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
70
126
  allowPackages: ['remix'],
71
127
  })
@@ -80,10 +136,6 @@ import { createAssetServer } from 'remix/assets'
80
136
 
81
137
  let assetServer = createAssetServer({
82
138
  basePath: '/assets',
83
- fileMap: {
84
- '/app/*path': 'app/*path',
85
- '/npm/*path': 'node_modules/*path',
86
- },
87
139
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
88
140
  allowPackages: ['remix'],
89
141
  denyFiles: ['app/**/*.server.*'],
@@ -92,27 +144,27 @@ let assetServer = createAssetServer({
92
144
 
93
145
  Values for `allowFiles` and `denyFiles` are file paths or globs. Relative values are resolved from `rootDir`. Absolute file paths match exactly, and absolute directory paths also match their descendants.
94
146
 
95
- Values for `allowPackages` are exact package names. Dependencies and installed optional dependencies of packages in `allowPackages` are also allowed automatically. Peer dependencies must be listed explicitly if they should be browser-reachable. Allowed package files must still be reachable through `fileMap`.
147
+ Values for `allowPackages` are exact package names. Dependencies and installed optional dependencies of packages in `allowPackages` are also allowed automatically. Peer dependencies must be listed explicitly if they should be browser-reachable. Allowed package files must still be reachable through `mounts`.
96
148
 
97
- ## File Map
149
+ ## Mounts
98
150
 
99
- Use `fileMap` to map public URLs to file paths on disk. `basePath` defines the shared public mount point, and the `fileMap` keys are URL patterns relative to that base path. The values are root-relative file path patterns.
151
+ By default, the asset server mounts the `app` directory at `/app` and `node_modules` at `/npm`. Use `mounts` to replace these defaults. Keys are public paths relative to `basePath`, and values are directory paths relative to `rootDir`.
100
152
 
101
153
  ```ts
102
154
  import { createAssetServer } from 'remix/assets'
103
155
 
104
156
  let assetServer = createAssetServer({
105
157
  basePath: '/assets',
106
- fileMap: {
107
- '/app/*path': 'app/*path',
108
- '/npm/*path': 'node_modules/*path',
158
+ mounts: {
159
+ source: 'app',
160
+ vendor: 'node_modules',
109
161
  },
110
162
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
111
163
  allowPackages: ['remix'],
112
164
  })
113
165
  ```
114
166
 
115
- `fileMap` entries use [`route-pattern`](https://github.com/remix-run/remix/tree/main/packages/route-pattern) syntax for both URL and file patterns. Wildcards must be named, and the same params must appear in both patterns so imports can be rewritten back to public URLs. For example, with `basePath: '/assets'`, a `fileMap` key of `'/app/*path'` is served at `/assets/app/*path`.
167
+ Package managers that install outside `rootDir`, such as pnpm's global virtual store, are handled without configuration: the asset server reads the store location from the nearest `node_modules/.modules.yaml` and mounts it internally so those package files still resolve to public URLs. A store that a configured mount already covers, such as pnpm's default `node_modules/.pnpm`, is left alone.
116
168
 
117
169
  ### File watching
118
170
 
@@ -123,10 +175,6 @@ import { createAssetServer } from 'remix/assets'
123
175
 
124
176
  let assetServer = createAssetServer({
125
177
  basePath: '/assets',
126
- fileMap: {
127
- '/app/*path': 'app/*path',
128
- '/npm/*path': 'node_modules/*path',
129
- },
130
178
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
131
179
  allowPackages: ['remix'],
132
180
  })
@@ -145,10 +193,6 @@ import { createAssetServer } from 'remix/assets'
145
193
 
146
194
  let assetServer = createAssetServer({
147
195
  basePath: '/assets',
148
- fileMap: {
149
- '/app/*path': 'app/*path',
150
- '/npm/*path': 'node_modules/*path',
151
- },
152
196
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
153
197
  allowPackages: ['remix'],
154
198
  watch: false,
@@ -162,10 +206,6 @@ import { createAssetServer } from 'remix/assets'
162
206
 
163
207
  let assetServer = createAssetServer({
164
208
  basePath: '/assets',
165
- fileMap: {
166
- '/app/*path': 'app/*path',
167
- '/npm/*path': 'node_modules/*path',
168
- },
169
209
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
170
210
  allowPackages: ['remix'],
171
211
  watch: {
@@ -174,13 +214,82 @@ let assetServer = createAssetServer({
174
214
  })
175
215
  ```
176
216
 
217
+ ## Script Entries
218
+
219
+ Use `assetServer.getScriptEntry()` to get everything needed to load a script and its dependencies.
220
+
221
+ ```ts
222
+ let { href, importMap, preloads } = await assetServer.getScriptEntry('app/assets/entry.tsx')
223
+ ```
224
+
225
+ This can be used when rendering a document shell:
226
+
227
+ ```tsx
228
+ import type { Handle, RemixNode } from 'remix/ui'
229
+ import { ImportMap } from 'remix/ui/server'
230
+ import { assetServer } from './assets.ts'
231
+
232
+ let { href, importMap, preloads } = await assetServer.getScriptEntry('app/assets/entry.tsx')
233
+
234
+ export function Document(handle: Handle<{ children: RemixNode }>) {
235
+ return () => (
236
+ <html>
237
+ <head>
238
+ {/* ... */}
239
+ <ImportMap value={importMap} />
240
+ {preloads.map((preload) => (
241
+ <link rel="modulepreload" href={preload} />
242
+ ))}
243
+ <script type="module" src={href} />
244
+ </head>
245
+ <body>{handle.props.children}</body>
246
+ </html>
247
+ )
248
+ }
249
+ ```
250
+
251
+ This can also be used for resolved client entries in [`remix/ui`](https://github.com/remix-run/remix/tree/main/packages/ui) when using `import.meta.url` as the client entry ID:
252
+
253
+ ```tsx
254
+ import { renderToStream } from 'remix/ui/server'
255
+ import { assetServer } from './assets.ts'
256
+
257
+ let stream = renderToStream(<App />, {
258
+ async resolveClientEntry(entryId, component) {
259
+ let { href, importMap, preloads } = await assetServer.getScriptEntry(entryId)
260
+
261
+ return {
262
+ href,
263
+ importMap,
264
+ preloads,
265
+ exportName: entryId.split('#')[1] || component.name,
266
+ }
267
+ },
268
+ // ...
269
+ })
270
+ ```
271
+
177
272
  ## Hrefs
178
273
 
179
274
  Use `assetServer.getHref()` when you need the public URL for a served asset. You can provide a root-relative or absolute file path, or a `file://` URL.
180
275
 
181
276
  ```ts
182
- let src = await assetServer.getHref('app/actions/public/entry.ts')
183
- // '/assets/app/actions/public/entry.ts'
277
+ let src = await assetServer.getHref('app/media/public/logo.svg')
278
+ // '/assets/app/media/public/logo.svg'
279
+ ```
280
+
281
+ ## Inspection
282
+
283
+ Use `getAssets()` for a sorted list of files that are currently browser-reachable through the
284
+ asset server. Use `getAssetDetails()` with a public URL or file path to inspect its mapping, file
285
+ type, access rules, and reachability status.
286
+
287
+ ```ts
288
+ let assets = await assetServer.getAssets()
289
+ // [{ url: '/assets/app/actions/public/entry.ts', filePath: '/project/app/actions/public/entry.ts', ... }]
290
+
291
+ let details = await assetServer.getAssetDetails('/assets/app/actions/public/entry.ts')
292
+ // { status: 'reachable', type: 'script', ... }
184
293
  ```
185
294
 
186
295
  For configured `files` assets, you can also pass a `transform` pipeline to build a request URL with custom file transforms. Basic transforms are written as strings, while dynamic transforms use `[name, param]` tuples.
@@ -192,6 +301,16 @@ let src = await assetServer.getHref('app/media/public/image.png', {
192
301
  // '/assets/app/media/public/image.png?transform=resize%3A100x100&transform=webp'
193
302
  ```
194
303
 
304
+ ## Import Maps
305
+
306
+ Scripts retain their imports as authored and rely on import maps for resolution in the browser. `assetServer.getScriptEntry()` returns the import map for a single rendered script entry. Use `assetServer.getImportMap()` directly when you need to generate a combined import map for multiple script roots or other custom graph-level behavior.
307
+
308
+ ```ts
309
+ let importMap = await assetServer.getImportMap(['app/assets/entry.tsx', 'app/assets/search.tsx'])
310
+ ```
311
+
312
+ Without fingerprinting, import maps resolve authored specifiers to stable asset URLs. With fingerprinting enabled, the same import maps resolve stable asset URLs to content-fingerprinted asset URLs.
313
+
195
314
  ## Preloads
196
315
 
197
316
  Use `assetServer.getPreloads()` when rendering HTML so you can turn the returned URLs into `<link rel="modulepreload">`, stylesheet preload tags, or `Link` headers for one or more assets and their dependencies. You can provide root-relative or absolute file paths, or `file://` URLs.
@@ -214,29 +333,23 @@ let preloads = await assetServer.getPreloads([
214
333
 
215
334
  By default, assets are served at stable URLs with ETags and `Cache-Control: no-cache`.
216
335
 
217
- If you want clients to cache assets aggressively without revalidation, you can opt into source-based fingerprinting.
336
+ If you want clients to cache assets aggressively without revalidation, you can opt into content-based fingerprinting.
218
337
 
219
338
  ```ts
220
339
  import { createAssetServer } from 'remix/assets'
221
340
 
222
341
  let assetServer = createAssetServer({
223
342
  basePath: '/assets',
224
- fileMap: {
225
- '/app/*path': 'app/*path',
226
- '/npm/*path': 'node_modules/*path',
227
- },
228
343
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
229
344
  allowPackages: ['remix'],
230
345
  watch: false,
231
- fingerprint: {
232
- buildId: process.env.GITHUB_SHA,
233
- },
346
+ fingerprint: true,
234
347
  })
235
348
  ```
236
349
 
237
350
  When fingerprinting is enabled, assets use a `.@<fingerprint>` segment before the file extension and are served with `Cache-Control: public, max-age=31536000, immutable`.
238
351
 
239
- Source fingerprints are based on the original file contents and the build ID. The build ID must change for each deployment so that fingerprinted assets are invalidated together. This fingerprinting strategy assumes that files on disk won't change, so fingerprinting requires `watch: false`.
352
+ Fingerprints are based on emitted asset contents. This allows unchanged assets to keep the same URL across deployments, but it assumes that files on disk won't change after a URL is generated, so fingerprinting requires `watch: false`.
240
353
 
241
354
  ## Target
242
355
 
@@ -247,10 +360,6 @@ import { createAssetServer } from 'remix/assets'
247
360
 
248
361
  let assetServer = createAssetServer({
249
362
  basePath: '/assets',
250
- fileMap: {
251
- '/app/*path': 'app/*path',
252
- '/npm/*path': 'node_modules/*path',
253
- },
254
363
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
255
364
  allowPackages: ['remix'],
256
365
  target: {
@@ -272,10 +381,6 @@ import { createAssetServer } from 'remix/assets'
272
381
 
273
382
  let assetServer = createAssetServer({
274
383
  basePath: '/assets',
275
- fileMap: {
276
- '/app/*path': 'app/*path',
277
- '/npm/*path': 'node_modules/*path',
278
- },
279
384
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
280
385
  allowPackages: ['remix'],
281
386
  sourceMaps: 'external',
@@ -289,10 +394,6 @@ import { createAssetServer } from 'remix/assets'
289
394
 
290
395
  let assetServer = createAssetServer({
291
396
  basePath: '/assets',
292
- fileMap: {
293
- '/app/*path': 'app/*path',
294
- '/npm/*path': 'node_modules/*path',
295
- },
296
397
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
297
398
  allowPackages: ['remix'],
298
399
  sourceMaps: 'inline',
@@ -309,10 +410,6 @@ import { createAssetServer } from 'remix/assets'
309
410
 
310
411
  let assetServer = createAssetServer({
311
412
  basePath: '/assets',
312
- fileMap: {
313
- '/app/*path': 'app/*path',
314
- '/npm/*path': 'node_modules/*path',
315
- },
316
413
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
317
414
  allowPackages: ['remix'],
318
415
  minify: true,
@@ -330,10 +427,6 @@ import { createAssetServer } from 'remix/assets'
330
427
 
331
428
  let assetServer = createAssetServer({
332
429
  basePath: '/assets',
333
- fileMap: {
334
- '/app/*path': 'app/*path',
335
- '/npm/*path': 'node_modules/*path',
336
- },
337
430
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
338
431
  allowPackages: ['remix'],
339
432
  scripts: {
@@ -355,10 +448,6 @@ import { createAssetServer } from 'remix/assets'
355
448
 
356
449
  let assetServer = createAssetServer({
357
450
  basePath: '/assets',
358
- fileMap: {
359
- '/app/*path': 'app/*path',
360
- '/npm/*path': 'node_modules/*path',
361
- },
362
451
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
363
452
  allowPackages: ['remix'],
364
453
  scripts: {
@@ -376,7 +465,6 @@ import { createAssetServer } from 'remix/assets'
376
465
 
377
466
  let assetServer = createAssetServer({
378
467
  basePath: '/assets',
379
- fileMap: { '/app/*path': 'app/*path' },
380
468
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
381
469
  denyFiles: ['app/**/*.test.*'],
382
470
  scripts: {
@@ -404,10 +492,6 @@ import { createAssetServer } from 'remix/assets'
404
492
 
405
493
  let assetServer = createAssetServer({
406
494
  basePath: '/assets',
407
- fileMap: {
408
- '/app/*path': 'app/*path',
409
- '/npm/*path': 'node_modules/*path',
410
- },
411
495
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
412
496
  allowPackages: ['remix'],
413
497
  files: {
@@ -430,10 +514,6 @@ import sharp from 'sharp'
430
514
 
431
515
  let assetServer = createAssetServer({
432
516
  basePath: '/assets',
433
- fileMap: {
434
- '/app/*path': 'app/*path',
435
- '/npm/*path': 'node_modules/*path',
436
- },
437
517
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
438
518
  allowPackages: ['remix'],
439
519
  files: {
@@ -464,10 +544,6 @@ import { createAssetServer, defineFileTransform } from 'remix/assets'
464
544
 
465
545
  let assetServer = createAssetServer({
466
546
  basePath: '/assets',
467
- fileMap: {
468
- '/app/*path': 'app/*path',
469
- '/npm/*path': 'node_modules/*path',
470
- },
471
547
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
472
548
  allowPackages: ['remix'],
473
549
  files: {
@@ -512,10 +588,6 @@ import { optimize as optimizeSvg } from 'svgo'
512
588
 
513
589
  let assetServer = createAssetServer({
514
590
  basePath: '/assets',
515
- fileMap: {
516
- '/app/*path': 'app/*path',
517
- '/npm/*path': 'node_modules/*path',
518
- },
519
591
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
520
592
  allowPackages: ['remix'],
521
593
  files: {
@@ -535,11 +607,9 @@ let assetServer = createAssetServer({
535
607
 
536
608
  #### File transform caching
537
609
 
538
- Use `files.cache` to store transformed file outputs via a [`file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage) backend.
610
+ Use `files.cache` to store transformed file outputs via a [`file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage) backend. Without `files.cache`, transformed file outputs are recomputed per request.
539
611
 
540
- Without `files.cache`, transformed file outputs are recomputed per request.
541
-
542
- If `fingerprint.buildId` is set, the file cache can be reused across server restarts for the same build.
612
+ `files.cacheKey` scopes transformed file cache entries. Use a stable identifier, such as a commit SHA, when you want unchanged transformed files to be reused across server restarts for the same build.
543
613
 
544
614
  ```ts
545
615
  import * as path from 'node:path'
@@ -548,14 +618,11 @@ import { createFsFileStorage } from 'remix/file-storage/fs'
548
618
 
549
619
  let assetServer = createAssetServer({
550
620
  basePath: '/assets',
551
- fileMap: {
552
- '/app/*path': 'app/*path',
553
- '/npm/*path': 'node_modules/*path',
554
- },
555
621
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
556
622
  allowPackages: ['remix'],
557
623
  files: {
558
624
  cache: createFsFileStorage(path.resolve('.tmp/assets-cache')),
625
+ cacheKey: process.env.GIT_COMMIT_SHA,
559
626
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
560
627
  transforms: {
561
628
  /*...*/
@@ -573,10 +640,6 @@ import { createAssetServer } from 'remix/assets'
573
640
 
574
641
  let assetServer = createAssetServer({
575
642
  basePath: '/assets',
576
- fileMap: {
577
- '/app/*path': 'app/*path',
578
- '/npm/*path': 'node_modules/*path',
579
- },
580
643
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
581
644
  allowPackages: ['remix'],
582
645
  files: {
@@ -624,10 +687,6 @@ import { createAssetServer } from 'remix/assets'
624
687
 
625
688
  let assetServer = createAssetServer({
626
689
  basePath: '/assets',
627
- fileMap: {
628
- '/app/*path': 'app/*path',
629
- '/npm/*path': 'node_modules/*path',
630
- },
631
690
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
632
691
  allowPackages: ['remix'],
633
692
  onError(error) {
@@ -651,9 +710,8 @@ import { createAssetServer } from 'remix/assets'
651
710
  let isDevelopment = process.env.NODE_ENV === 'development'
652
711
  let assetServer = createAssetServer({
653
712
  basePath: '/assets',
654
- fileMap: { '/app/*path': 'app/*path' },
655
713
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
656
- denyFiles: ['app/**/*.test.*'],
714
+ allowPackages: ['remix'],
657
715
  hmr: isDevelopment
658
716
  ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
659
717
  : undefined,
@@ -661,6 +719,30 @@ let assetServer = createAssetServer({
661
719
  })
662
720
  ```
663
721
 
722
+ Use `moduleImporter` to customize how HMR dynamically imports updated browser modules. It is resolved relative to the asset server's root directory and must point to a browser module exporting:
723
+
724
+ ```ts
725
+ export function importModule(specifier: string, parentUrl: string): Promise<Record<string, unknown>>
726
+ ```
727
+
728
+ HMR appends mappings for updated modules to the document in additional `<script type="importmap">` elements. Use `remix/multiple-import-maps-polyfill` when these updates must work in browsers without native support for multiple import maps:
729
+
730
+ ```ts
731
+ import { createAssetServer } from 'remix/assets'
732
+ import { createBrowserHmrChannel } from 'remix/node-hmr/runtime'
733
+
734
+ let assetServer = createAssetServer({
735
+ basePath: '/assets',
736
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
737
+ allowPackages: ['remix'],
738
+ hmr: {
739
+ channel: createBrowserHmrChannel,
740
+ moduleImporter: 'remix/multiple-import-maps-polyfill',
741
+ },
742
+ watch: true,
743
+ })
744
+ ```
745
+
664
746
  ### `import.meta.hot`
665
747
 
666
748
  The `import.meta.hot` API provided by `assets` is a small runtime contract for modules that can handle updates without reloading the page. It is primarily intended for browser modules compiled by `assets`, but it can also be used directly.
@@ -798,7 +880,6 @@ if (import.meta.hot) {
798
880
  - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - A Fetch-based router that pairs naturally with `assets`
799
881
  - [`node-hmr`](https://github.com/remix-run/remix/tree/main/packages/node-hmr) - Provides the server-side `import.meta.hot` runtime and browser HMR channel used by `hmr`
800
882
  - [`ui-hmr`](https://github.com/remix-run/remix/tree/main/packages/ui-hmr) - Provides a Remix UI component HMR loader for `scripts.loaders`
801
- - [`route-pattern`](https://github.com/remix-run/remix/tree/main/packages/route-pattern) - Route-pattern syntax for URL and route file matching
802
883
 
803
884
  ## License
804
885
 
package/dist/assets.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export { createAssetServer } from './lib/asset-server.ts';
2
2
  export { defineFileTransform } from './lib/files/config.ts';
3
- export type { AssetServer, AssetServerOptions, BrowserHmrChannel } from './lib/asset-server.ts';
3
+ export type { AssetAccessDetails, AssetAccessRule } from './lib/access.ts';
4
+ export type { AssetServer, AssetServerOptions, BrowserHmrChannel, ScriptEntry, } from './lib/asset-server.ts';
5
+ export type { AssetDetails, AssetKind, AssetStatus } from './lib/inspection.ts';
6
+ export type { ScriptImportMap } from './lib/scripts/compiler.ts';
4
7
  export type { ModuleLoader } from './lib/loaders.ts';
5
8
  //# sourceMappingURL=assets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC/F,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC1E,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC/E,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA"}
@@ -1,6 +1,34 @@
1
- type AccessPolicy = {
1
+ /** Access-policy result for an inspected asset file. */
2
+ export interface AssetAccessDetails {
3
+ /** Whether the asset server may serve the file. */
4
+ allowed: boolean;
5
+ /** The first configured rule that allowed the file, when one matched. */
6
+ allowedBy?: AssetAccessRule;
7
+ /** The first matching `denyFiles` pattern, when access was denied. */
8
+ deniedBy?: string;
9
+ }
10
+ /** Rule that allows an inspected asset file to be served. */
11
+ export type AssetAccessRule =
12
+ /** A matching `allowFiles` entry. */
13
+ {
14
+ kind: 'file';
15
+ value: string;
16
+ }
17
+ /** A runtime file provided internally by the asset server. */
18
+ | {
19
+ kind: 'injected';
20
+ value: string;
21
+ }
22
+ /** A matching `allowPackages` entry. */
23
+ | {
24
+ kind: 'package';
25
+ value: string;
26
+ };
27
+ export type AccessPolicy = {
28
+ getAllowedPackageRoots(): readonly string[];
2
29
  getPackageWatchDirectories(): readonly string[];
3
30
  handleFileEvent(filePath: string): void;
31
+ inspect(filePath: string): AssetAccessDetails;
4
32
  isAllowed(filePath: string): boolean;
5
33
  };
6
34
  export declare function createAccessPolicy(options: {
@@ -10,5 +38,4 @@ export declare function createAccessPolicy(options: {
10
38
  packageSearchRoots?: readonly string[];
11
39
  rootDir: string;
12
40
  }): AccessPolicy;
13
- export {};
14
41
  //# sourceMappingURL=access.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/lib/access.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG;IAClB,0BAA0B,IAAI,SAAS,MAAM,EAAE,CAAA;IAC/C,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;CACrC,CAAA;AAcD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,YAAY,CAwDf"}
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/lib/access.ts"],"names":[],"mappings":"AAMA,wDAAwD;AACxD,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,OAAO,EAAE,OAAO,CAAA;IAChB,yEAAyE;IACzE,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,6DAA6D;AAC7D,MAAM,MAAM,eAAe;AACzB,qCAAqC;AACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACjC,8DAA8D;GAC5D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACrC,wCAAwC;GACtC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtC,MAAM,MAAM,YAAY,GAAG;IACzB,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAC3C,0BAA0B,IAAI,SAAS,MAAM,EAAE,CAAA;IAC/C,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAAA;IAC7C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;CACrC,CAAA;AAcD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,YAAY,CAoFf"}