@remix-run/assets 0.4.3 → 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 (76) 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.d.ts.map +1 -1
  29. package/dist/lib/routes.js +13 -13
  30. package/dist/lib/scripts/compiler.d.ts +24 -1
  31. package/dist/lib/scripts/compiler.d.ts.map +1 -1
  32. package/dist/lib/scripts/compiler.js +183 -29
  33. package/dist/lib/scripts/conditions.d.ts +2 -0
  34. package/dist/lib/scripts/conditions.d.ts.map +1 -0
  35. package/dist/lib/scripts/conditions.js +1 -0
  36. package/dist/lib/scripts/emit.d.ts +3 -0
  37. package/dist/lib/scripts/emit.d.ts.map +1 -1
  38. package/dist/lib/scripts/emit.js +24 -5
  39. package/dist/lib/scripts/resolve.d.ts +4 -0
  40. package/dist/lib/scripts/resolve.d.ts.map +1 -1
  41. package/dist/lib/scripts/resolve.js +70 -5
  42. package/dist/lib/scripts/transform.d.ts +9 -0
  43. package/dist/lib/scripts/transform.d.ts.map +1 -1
  44. package/dist/lib/scripts/transform.js +222 -18
  45. package/dist/lib/source-maps.js +1 -1
  46. package/dist/lib/styles/compiler.d.ts +14 -1
  47. package/dist/lib/styles/compiler.d.ts.map +1 -1
  48. package/dist/lib/styles/compiler.js +78 -14
  49. package/dist/lib/styles/emit.js +4 -4
  50. package/dist/lib/styles/resolve.d.ts.map +1 -1
  51. package/dist/lib/styles/resolve.js +8 -7
  52. package/dist/lib/styles/transform.js +3 -3
  53. package/dist/lib/target.d.ts +1 -1
  54. package/dist/lib/target.d.ts.map +1 -1
  55. package/dist/lib/watch.js +1 -1
  56. package/dist/types/hmr.d.ts +36 -0
  57. package/package.json +17 -11
  58. package/src/assets.ts +2 -1
  59. package/src/lib/access.ts +386 -5
  60. package/src/lib/asset-server.ts +429 -18
  61. package/src/lib/compilation-error.ts +1 -0
  62. package/src/lib/files/compiler.ts +7 -3
  63. package/src/lib/hmr.ts +397 -0
  64. package/src/lib/injected-packages.ts +41 -11
  65. package/src/lib/loaders.ts +80 -0
  66. package/src/lib/module-store.ts +190 -9
  67. package/src/lib/routes.ts +24 -14
  68. package/src/lib/scripts/compiler.ts +248 -24
  69. package/src/lib/scripts/conditions.ts +1 -0
  70. package/src/lib/scripts/emit.ts +50 -5
  71. package/src/lib/scripts/resolve.ts +124 -2
  72. package/src/lib/scripts/transform.ts +290 -19
  73. package/src/lib/styles/compiler.ts +108 -8
  74. package/src/lib/styles/emit.ts +1 -1
  75. package/src/lib/styles/resolve.ts +11 -7
  76. package/src/types/hmr.d.ts +36 -0
package/README.md CHANGED
@@ -7,11 +7,13 @@ Fetch-based server for compiling browser assets on demand.
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
9
  - **Custom File Mapping** - Define patterns for mapping public URLs to file paths on disk
10
- - **Access Control** - Control exactly which files can be served with allow and deny rules
10
+ - **Access Control** - Control exactly which files and packages can be served
11
11
  - **Preloads** - Generate preload URLs for scripts and styles based on imports
12
12
  - **Caching** - Conservative caching by default with stable URLs, ETags, and revalidation
13
13
  - **Optional Fingerprinting** - Source-based fingerprinted URLs for long-lived browser caching
14
14
  - **Source Maps** - Serve inline or external sourcemaps
15
+ - **Hot Module Reloading** - Handle live code updates in development
16
+ - **Script Loaders** - Post-process compiled JavaScript with Node-compatible loaders
15
17
 
16
18
  ## Installation
17
19
 
@@ -33,7 +35,8 @@ let assetServer = createAssetServer({
33
35
  '/app/*path': 'app/*path',
34
36
  '/npm/*path': 'node_modules/*path',
35
37
  },
36
- allow: ['app/assets/**', 'node_modules/**'],
38
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
39
+ allowPackages: ['remix'],
37
40
  files: {
38
41
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
39
42
  },
@@ -46,7 +49,7 @@ router.get('/assets/*', ({ request }) => {
46
49
  })
47
50
  ```
48
51
 
49
- This example gives you an `/assets/*` endpoint that serves compiled browser assets from `app/assets` and `node_modules`.
52
+ This example gives you an `/assets/*` endpoint that serves compiled browser source from `public/` directories throughout `app/` and from the `remix` package.
50
53
 
51
54
  ## Root Directory
52
55
 
@@ -63,26 +66,33 @@ let assetServer = createAssetServer({
63
66
  '/app/*path': 'app/*path',
64
67
  '/npm/*path': 'node_modules/*path',
65
68
  },
66
- allow: ['app/assets/**', 'node_modules/**'],
69
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
70
+ allowPackages: ['remix'],
67
71
  })
68
72
  ```
69
73
 
70
74
  ## Access Control
71
75
 
72
- You must provide an `allow` list to specify which files are allowed to be served. `deny` is optional and takes precedence over `allow`.
76
+ You must provide an `allowFiles` list to specify which files are allowed to be served. You can also allow whole packages by name with `allowPackages`. `denyFiles` is optional and takes precedence over both `allowFiles` and `allowPackages`.
73
77
 
74
78
  ```ts
75
79
  import { createAssetServer } from 'remix/assets'
76
80
 
77
81
  let assetServer = createAssetServer({
78
82
  basePath: '/assets',
79
- fileMap: { '/app/*path': 'app/*path' },
80
- allow: ['app/assets/**'],
81
- deny: ['app/**/*.server.*'],
83
+ fileMap: {
84
+ '/app/*path': 'app/*path',
85
+ '/npm/*path': 'node_modules/*path',
86
+ },
87
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
88
+ allowPackages: ['remix'],
89
+ denyFiles: ['app/**/*.server.*'],
82
90
  })
83
91
  ```
84
92
 
85
- Rules for `allow` and `deny` are file paths or globs. Relative values are resolved from `rootDir`. Absolute file paths match exactly, and absolute directory paths also match their descendants.
93
+ 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
+
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`.
86
96
 
87
97
  ## File Map
88
98
 
@@ -95,9 +105,10 @@ let assetServer = createAssetServer({
95
105
  basePath: '/assets',
96
106
  fileMap: {
97
107
  '/app/*path': 'app/*path',
98
- '/packages/*path': '../packages/*path',
108
+ '/npm/*path': 'node_modules/*path',
99
109
  },
100
- allow: ['app/assets/**', '../packages/**'],
110
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
111
+ allowPackages: ['remix'],
101
112
  })
102
113
  ```
103
114
 
@@ -112,8 +123,12 @@ import { createAssetServer } from 'remix/assets'
112
123
 
113
124
  let assetServer = createAssetServer({
114
125
  basePath: '/assets',
115
- fileMap: { '/app/*path': 'app/*path' },
116
- allow: ['app/assets/**', 'app/node_modules/**'],
126
+ fileMap: {
127
+ '/app/*path': 'app/*path',
128
+ '/npm/*path': 'node_modules/*path',
129
+ },
130
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
131
+ allowPackages: ['remix'],
117
132
  })
118
133
  ```
119
134
 
@@ -130,8 +145,12 @@ import { createAssetServer } from 'remix/assets'
130
145
 
131
146
  let assetServer = createAssetServer({
132
147
  basePath: '/assets',
133
- fileMap: { '/app/*path': 'app/*path' },
134
- allow: ['app/assets/**', 'app/node_modules/**'],
148
+ fileMap: {
149
+ '/app/*path': 'app/*path',
150
+ '/npm/*path': 'node_modules/*path',
151
+ },
152
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
153
+ allowPackages: ['remix'],
135
154
  watch: false,
136
155
  })
137
156
  ```
@@ -143,8 +162,12 @@ import { createAssetServer } from 'remix/assets'
143
162
 
144
163
  let assetServer = createAssetServer({
145
164
  basePath: '/assets',
146
- fileMap: { '/app/*path': 'app/*path' },
147
- allow: ['app/assets/**', 'app/node_modules/**'],
165
+ fileMap: {
166
+ '/app/*path': 'app/*path',
167
+ '/npm/*path': 'node_modules/*path',
168
+ },
169
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
170
+ allowPackages: ['remix'],
148
171
  watch: {
149
172
  ignore: ['**/node_modules/**'],
150
173
  },
@@ -156,17 +179,17 @@ let assetServer = createAssetServer({
156
179
  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.
157
180
 
158
181
  ```ts
159
- let src = await assetServer.getHref('app/assets/entry.tsx')
160
- // '/assets/app/assets/entry.tsx'
182
+ let src = await assetServer.getHref('app/actions/public/entry.ts')
183
+ // '/assets/app/actions/public/entry.ts'
161
184
  ```
162
185
 
163
186
  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.
164
187
 
165
188
  ```ts
166
- let src = await assetServer.getHref('app/assets/image.png', {
189
+ let src = await assetServer.getHref('app/media/public/image.png', {
167
190
  transform: [['resize', '100x100'], 'webp'],
168
191
  })
169
- // '/assets/app/assets/image.png?transform=resize%3A100x100&transform=webp'
192
+ // '/assets/app/media/public/image.png?transform=resize%3A100x100&transform=webp'
170
193
  ```
171
194
 
172
195
  ## Preloads
@@ -174,11 +197,14 @@ let src = await assetServer.getHref('app/assets/image.png', {
174
197
  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.
175
198
 
176
199
  ```ts
177
- let preloads = await assetServer.getPreloads(['app/assets/entry.tsx', 'app/assets/search.tsx'])
200
+ let preloads = await assetServer.getPreloads([
201
+ 'app/actions/public/entry.ts',
202
+ 'app/search/public/search.tsx',
203
+ ])
178
204
  // [
179
- // '/assets/app/assets/entry.tsx',
180
- // '/assets/app/assets/search.tsx',
181
- // '/assets/app/assets/utils.ts',
205
+ // '/assets/app/actions/public/entry.ts',
206
+ // '/assets/app/search/public/search.tsx',
207
+ // '/assets/app/search/public/utils.ts',
182
208
  // '/assets/npm/remix/ui/index.js',
183
209
  // ...etc
184
210
  // ]
@@ -195,8 +221,12 @@ import { createAssetServer } from 'remix/assets'
195
221
 
196
222
  let assetServer = createAssetServer({
197
223
  basePath: '/assets',
198
- fileMap: { '/app/*path': 'app/*path' },
199
- allow: ['app/assets/**'],
224
+ fileMap: {
225
+ '/app/*path': 'app/*path',
226
+ '/npm/*path': 'node_modules/*path',
227
+ },
228
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
229
+ allowPackages: ['remix'],
200
230
  watch: false,
201
231
  fingerprint: {
202
232
  buildId: process.env.GITHUB_SHA,
@@ -217,8 +247,12 @@ import { createAssetServer } from 'remix/assets'
217
247
 
218
248
  let assetServer = createAssetServer({
219
249
  basePath: '/assets',
220
- fileMap: { '/app/*path': 'app/*path' },
221
- allow: ['app/assets/**'],
250
+ fileMap: {
251
+ '/app/*path': 'app/*path',
252
+ '/npm/*path': 'node_modules/*path',
253
+ },
254
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
255
+ allowPackages: ['remix'],
222
256
  target: {
223
257
  chrome: '109',
224
258
  ios: '15.6',
@@ -238,8 +272,12 @@ import { createAssetServer } from 'remix/assets'
238
272
 
239
273
  let assetServer = createAssetServer({
240
274
  basePath: '/assets',
241
- fileMap: { '/app/*path': 'app/*path' },
242
- allow: ['app/assets/**'],
275
+ fileMap: {
276
+ '/app/*path': 'app/*path',
277
+ '/npm/*path': 'node_modules/*path',
278
+ },
279
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
280
+ allowPackages: ['remix'],
243
281
  sourceMaps: 'external',
244
282
  })
245
283
  ```
@@ -251,8 +289,12 @@ import { createAssetServer } from 'remix/assets'
251
289
 
252
290
  let assetServer = createAssetServer({
253
291
  basePath: '/assets',
254
- fileMap: { '/app/*path': 'app/*path' },
255
- allow: ['app/assets/**'],
292
+ fileMap: {
293
+ '/app/*path': 'app/*path',
294
+ '/npm/*path': 'node_modules/*path',
295
+ },
296
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
297
+ allowPackages: ['remix'],
256
298
  sourceMaps: 'inline',
257
299
  sourceMapSourcePaths: 'absolute',
258
300
  })
@@ -267,8 +309,12 @@ import { createAssetServer } from 'remix/assets'
267
309
 
268
310
  let assetServer = createAssetServer({
269
311
  basePath: '/assets',
270
- fileMap: { '/app/*path': 'app/*path' },
271
- allow: ['app/assets/**'],
312
+ fileMap: {
313
+ '/app/*path': 'app/*path',
314
+ '/npm/*path': 'node_modules/*path',
315
+ },
316
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
317
+ allowPackages: ['remix'],
272
318
  minify: true,
273
319
  })
274
320
  ```
@@ -284,8 +330,12 @@ import { createAssetServer } from 'remix/assets'
284
330
 
285
331
  let assetServer = createAssetServer({
286
332
  basePath: '/assets',
287
- fileMap: { '/app/*path': 'app/*path' },
288
- allow: ['app/assets/**', 'app/node_modules/**'],
333
+ fileMap: {
334
+ '/app/*path': 'app/*path',
335
+ '/npm/*path': 'node_modules/*path',
336
+ },
337
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
338
+ allowPackages: ['remix'],
289
339
  scripts: {
290
340
  define: {
291
341
  'process.env.NODE_ENV': '"production"',
@@ -305,14 +355,46 @@ import { createAssetServer } from 'remix/assets'
305
355
 
306
356
  let assetServer = createAssetServer({
307
357
  basePath: '/assets',
308
- fileMap: { '/app/*path': 'app/*path' },
309
- allow: ['app/assets/**'],
358
+ fileMap: {
359
+ '/app/*path': 'app/*path',
360
+ '/npm/*path': 'node_modules/*path',
361
+ },
362
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
363
+ allowPackages: ['remix'],
310
364
  scripts: {
311
365
  external: ['my-external-import'],
312
366
  },
313
367
  })
314
368
  ```
315
369
 
370
+ ### Loaders
371
+
372
+ Use `scripts.loaders` to post-process compiled JavaScript. Loaders use the same function signature and chaining behavior as synchronous [`load` hooks in Node's module API](https://nodejs.org/api/module.html#customization-hooks).
373
+
374
+ ```ts
375
+ import { createAssetServer } from 'remix/assets'
376
+
377
+ let assetServer = createAssetServer({
378
+ basePath: '/assets',
379
+ fileMap: { '/app/*path': 'app/*path' },
380
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
381
+ denyFiles: ['app/**/*.test.*'],
382
+ scripts: {
383
+ loaders: [
384
+ (url, context, nextLoad) => {
385
+ let result = nextLoad(url, context)
386
+ return {
387
+ ...result,
388
+ source: `${result.source}\nconsole.log('loaded')`,
389
+ }
390
+ },
391
+ ],
392
+ },
393
+ })
394
+ ```
395
+
396
+ Loaders receive JavaScript after the asset server transforms TypeScript and JavaScript, and run before HMR analysis and minification. They must return `format: 'module'`. Import attributes are not supported.
397
+
316
398
  ## File Options
317
399
 
318
400
  Use `files` to serve additional leaf assets like images and fonts. File extensions must include the leading dot and are only served when explicitly configured.
@@ -322,8 +404,12 @@ import { createAssetServer } from 'remix/assets'
322
404
 
323
405
  let assetServer = createAssetServer({
324
406
  basePath: '/assets',
325
- fileMap: { '/app/*path': 'app/*path' },
326
- allow: ['app/assets/**'],
407
+ fileMap: {
408
+ '/app/*path': 'app/*path',
409
+ '/npm/*path': 'node_modules/*path',
410
+ },
411
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
412
+ allowPackages: ['remix'],
327
413
  files: {
328
414
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
329
415
  },
@@ -344,8 +430,12 @@ import sharp from 'sharp'
344
430
 
345
431
  let assetServer = createAssetServer({
346
432
  basePath: '/assets',
347
- fileMap: { '/app/*path': 'app/*path' },
348
- allow: ['app/assets/**'],
433
+ fileMap: {
434
+ '/app/*path': 'app/*path',
435
+ '/npm/*path': 'node_modules/*path',
436
+ },
437
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
438
+ allowPackages: ['remix'],
349
439
  files: {
350
440
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
351
441
  transforms: {
@@ -362,7 +452,7 @@ let assetServer = createAssetServer({
362
452
  },
363
453
  })
364
454
 
365
- let imageUrl = await assetServer.getHref('app/assets/photo.jpg', {
455
+ let imageUrl = await assetServer.getHref('app/media/public/photo.jpg', {
366
456
  transform: ['webp'],
367
457
  })
368
458
  ```
@@ -374,8 +464,12 @@ import { createAssetServer, defineFileTransform } from 'remix/assets'
374
464
 
375
465
  let assetServer = createAssetServer({
376
466
  basePath: '/assets',
377
- fileMap: { '/app/*path': 'app/*path' },
378
- allow: ['app/assets/**'],
467
+ fileMap: {
468
+ '/app/*path': 'app/*path',
469
+ '/npm/*path': 'node_modules/*path',
470
+ },
471
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
472
+ allowPackages: ['remix'],
379
473
  files: {
380
474
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
381
475
  transforms: {
@@ -395,7 +489,7 @@ let assetServer = createAssetServer({
395
489
  },
396
490
  })
397
491
 
398
- let imageUrl = await assetServer.getHref('app/assets/logo.svg', {
492
+ let imageUrl = await assetServer.getHref('app/media/public/logo.svg', {
399
493
  transform: [['recolor', '0000ff']],
400
494
  })
401
495
  ```
@@ -404,7 +498,7 @@ Hand-authored URLs use repeated `transform` search params with `name` or `name:p
404
498
 
405
499
  ```css
406
500
  .selector {
407
- background-image: url('/assets/app/assets/image.png?transform=resize:100x100&transform=webp');
501
+ background-image: url('/assets/app/media/public/image.png?transform=resize:100x100&transform=webp');
408
502
  }
409
503
  ```
410
504
 
@@ -418,8 +512,12 @@ import { optimize as optimizeSvg } from 'svgo'
418
512
 
419
513
  let assetServer = createAssetServer({
420
514
  basePath: '/assets',
421
- fileMap: { '/app/*path': 'app/*path' },
422
- allow: ['app/assets/**'],
515
+ fileMap: {
516
+ '/app/*path': 'app/*path',
517
+ '/npm/*path': 'node_modules/*path',
518
+ },
519
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
520
+ allowPackages: ['remix'],
423
521
  files: {
424
522
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
425
523
  globalTransforms: [
@@ -450,8 +548,12 @@ import { createFsFileStorage } from 'remix/file-storage/fs'
450
548
 
451
549
  let assetServer = createAssetServer({
452
550
  basePath: '/assets',
453
- fileMap: { '/app/*path': 'app/*path' },
454
- allow: ['app/assets/**'],
551
+ fileMap: {
552
+ '/app/*path': 'app/*path',
553
+ '/npm/*path': 'node_modules/*path',
554
+ },
555
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
556
+ allowPackages: ['remix'],
455
557
  files: {
456
558
  cache: createFsFileStorage(path.resolve('.tmp/assets-cache')),
457
559
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
@@ -471,8 +573,12 @@ import { createAssetServer } from 'remix/assets'
471
573
 
472
574
  let assetServer = createAssetServer({
473
575
  basePath: '/assets',
474
- fileMap: { '/app/*path': 'app/*path' },
475
- allow: ['app/assets/**'],
576
+ fileMap: {
577
+ '/app/*path': 'app/*path',
578
+ '/npm/*path': 'node_modules/*path',
579
+ },
580
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
581
+ allowPackages: ['remix'],
476
582
  files: {
477
583
  maxRequestTransforms: 5,
478
584
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
@@ -518,8 +624,12 @@ import { createAssetServer } from 'remix/assets'
518
624
 
519
625
  let assetServer = createAssetServer({
520
626
  basePath: '/assets',
521
- fileMap: { '/app/*path': 'app/*path' },
522
- allow: ['app/assets/**'],
627
+ fileMap: {
628
+ '/app/*path': 'app/*path',
629
+ '/npm/*path': 'node_modules/*path',
630
+ },
631
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
632
+ allowPackages: ['remix'],
523
633
  onError(error) {
524
634
  console.error('Failed to build client assets', error)
525
635
  return new Response('Client asset build failed', { status: 500 })
@@ -529,9 +639,165 @@ let assetServer = createAssetServer({
529
639
 
530
640
  If `onError` returns nothing, the asset server responds with the default `500 Internal Server Error` response.
531
641
 
642
+ ## Hot Module Reloading
643
+
644
+ Use `hmr` with `watch` to enable the `import.meta.hot` API for browser modules. The `hmr` option is designed for integrating assets with a server-level HMR runtime such as [`node-hmr`](https://github.com/remix-run/remix/tree/main/packages/node-hmr) so server and browser updates can be coordinated.
645
+
646
+ The `hmr` option accepts an async function that creates a `BrowserHmrChannel`, such as the `createBrowserHmrChannel` function from [`node-hmr`](https://github.com/remix-run/remix/tree/main/packages/node-hmr):
647
+
648
+ ```ts
649
+ import { createAssetServer } from 'remix/assets'
650
+
651
+ let isDevelopment = process.env.NODE_ENV === 'development'
652
+ let assetServer = createAssetServer({
653
+ basePath: '/assets',
654
+ fileMap: { '/app/*path': 'app/*path' },
655
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
656
+ denyFiles: ['app/**/*.test.*'],
657
+ hmr: isDevelopment
658
+ ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
659
+ : undefined,
660
+ watch: isDevelopment,
661
+ })
662
+ ```
663
+
664
+ ### `import.meta.hot`
665
+
666
+ 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.
667
+
668
+ To type `import.meta.hot`, add the HMR types to your TypeScript config:
669
+
670
+ ```json
671
+ {
672
+ "compilerOptions": {
673
+ "types": ["remix/assets/types/hmr"]
674
+ }
675
+ }
676
+ ```
677
+
678
+ HMR accept calls are statically analyzed. Write them directly as `import.meta.hot.accept(...)`. Dependency accepts must use string literals or arrays of string literals; do not alias `import.meta.hot` or pass dynamically constructed dependency lists.
679
+
680
+ ```ts
681
+ if (import.meta.hot) {
682
+ import.meta.hot.accept()
683
+ }
684
+ ```
685
+
686
+ ### Accepting updates
687
+
688
+ Calling `accept()` makes the current module an HMR boundary. When the module changes, `assets` evaluates the updated module and calls your callback with its exports.
689
+
690
+ ```ts
691
+ export let value = 1
692
+
693
+ if (import.meta.hot) {
694
+ import.meta.hot.accept((module) => {
695
+ if (typeof module.value !== 'number') {
696
+ import.meta.hot?.invalidate('Updated module no longer exports value')
697
+ return
698
+ }
699
+
700
+ value = module.value
701
+ })
702
+ }
703
+ ```
704
+
705
+ You can also accept updates from direct dependencies.
706
+
707
+ ```ts
708
+ import { value } from './value.ts'
709
+
710
+ let currentValue = value
711
+
712
+ export function readValue() {
713
+ return currentValue
714
+ }
715
+
716
+ if (import.meta.hot) {
717
+ import.meta.hot.accept('./value.ts', (module) => {
718
+ if (typeof module.value !== 'number') {
719
+ import.meta.hot?.invalidate('Updated dependency no longer exports value')
720
+ return
721
+ }
722
+
723
+ currentValue = module.value
724
+ })
725
+ }
726
+ ```
727
+
728
+ Multiple dependencies can be accepted at once. The callback receives an array where only the changed dependency is defined.
729
+
730
+ ```ts
731
+ if (import.meta.hot) {
732
+ import.meta.hot.accept(['./one.ts', './two.ts'], ([oneModule, twoModule]) => {
733
+ // oneModule is defined when ./one.ts changed.
734
+ // twoModule is defined when ./two.ts changed.
735
+ })
736
+ }
737
+ ```
738
+
739
+ ### Cleaning up
740
+
741
+ Register cleanup that should run before the module is replaced or disposed.
742
+
743
+ ```ts
744
+ let interval = setInterval(refreshCache, 30_000)
745
+
746
+ if (import.meta.hot) {
747
+ import.meta.hot.dispose(() => {
748
+ clearInterval(interval)
749
+ })
750
+ }
751
+ ```
752
+
753
+ The `data` object is preserved across updates for the same module. Use it for small pieces of state.
754
+
755
+ ```ts
756
+ let count = Number(import.meta.hot?.data.count ?? 0)
757
+
758
+ export function increment() {
759
+ count++
760
+ }
761
+
762
+ if (import.meta.hot) {
763
+ import.meta.hot.dispose((data) => {
764
+ data.count = count
765
+ })
766
+ }
767
+ ```
768
+
769
+ ### Invalidating updates
770
+
771
+ Call `invalidate()` inside an accept callback when the update cannot be applied safely. If no other boundary accepts the update, the browser reloads.
772
+
773
+ ```ts
774
+ if (import.meta.hot) {
775
+ import.meta.hot.accept((module) => {
776
+ if (typeof module.value !== 'number') {
777
+ import.meta.hot?.invalidate('Updated module no longer exports value')
778
+ return
779
+ }
780
+ })
781
+ }
782
+ ```
783
+
784
+ ### Server update events
785
+
786
+ When the browser HMR channel comes from `remix/node-hmr/runtime`, server updates are sent to browser modules as `server:update` events.
787
+
788
+ ```ts
789
+ if (import.meta.hot) {
790
+ import.meta.hot.on('server:update', () => {
791
+ window.location.reload()
792
+ })
793
+ }
794
+ ```
795
+
532
796
  ## Related Packages
533
797
 
534
798
  - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - A Fetch-based router that pairs naturally with `assets`
799
+ - [`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
+ - [`ui-hmr`](https://github.com/remix-run/remix/tree/main/packages/ui-hmr) - Provides a Remix UI component HMR loader for `scripts.loaders`
535
801
  - [`route-pattern`](https://github.com/remix-run/remix/tree/main/packages/route-pattern) - Route-pattern syntax for URL and route file matching
536
802
 
537
803
  ## License
package/dist/assets.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { createAssetServer } from './lib/asset-server.ts';
2
2
  export { defineFileTransform } from './lib/files/config.ts';
3
- export type { AssetServer, AssetServerOptions } from './lib/asset-server.ts';
3
+ export type { AssetServer, AssetServerOptions, BrowserHmrChannel } from './lib/asset-server.ts';
4
+ export type { ModuleLoader } from './lib/loaders.ts';
4
5
  //# 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,MAAM,uBAAuB,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,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC/F,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA"}
package/dist/assets.js CHANGED
@@ -1,2 +1,2 @@
1
- export { createAssetServer } from "./lib/asset-server.js";
2
- export { defineFileTransform } from "./lib/files/config.js";
1
+ export { createAssetServer } from './lib/asset-server.js';
2
+ export { defineFileTransform } from './lib/files/config.js';
@@ -1,9 +1,13 @@
1
1
  type AccessPolicy = {
2
+ getPackageWatchDirectories(): readonly string[];
3
+ handleFileEvent(filePath: string): void;
2
4
  isAllowed(filePath: string): boolean;
3
5
  };
4
6
  export declare function createAccessPolicy(options: {
5
- allow: readonly string[];
6
- deny?: readonly string[];
7
+ allowFiles: readonly string[];
8
+ allowPackages?: readonly string[];
9
+ denyFiles?: readonly string[];
10
+ packageSearchRoots?: readonly string[];
7
11
  rootDir: string;
8
12
  }): AccessPolicy;
9
13
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/lib/access.ts"],"names":[],"mappings":"AAGA,KAAK,YAAY,GAAG;IAClB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;CACrC,CAAA;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,YAAY,CAcf"}
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"}