@remix-run/assets 0.4.4 → 0.6.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 (83) hide show
  1. package/README.md +331 -72
  2. package/dist/assets.d.ts +4 -1
  3. package/dist/assets.d.ts.map +1 -1
  4. package/dist/assets.js +2 -2
  5. package/dist/lib/access.d.ts +35 -4
  6. package/dist/lib/access.d.ts.map +1 -1
  7. package/dist/lib/access.js +332 -11
  8. package/dist/lib/asset-server.d.ts +138 -11
  9. package/dist/lib/asset-server.d.ts.map +1 -1
  10. package/dist/lib/asset-server.js +281 -30
  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 +9 -8
  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 +3 -2
  21. package/dist/lib/injected-packages.d.ts.map +1 -1
  22. package/dist/lib/injected-packages.js +43 -17
  23. package/dist/lib/inspection.d.ts +39 -0
  24. package/dist/lib/inspection.d.ts.map +1 -0
  25. package/dist/lib/inspection.js +160 -0
  26. package/dist/lib/loaders.d.ts +57 -0
  27. package/dist/lib/loaders.d.ts.map +1 -0
  28. package/dist/lib/loaders.js +1 -0
  29. package/dist/lib/module-store.d.ts +24 -0
  30. package/dist/lib/module-store.d.ts.map +1 -1
  31. package/dist/lib/module-store.js +136 -11
  32. package/dist/lib/routes.d.ts +11 -3
  33. package/dist/lib/routes.d.ts.map +1 -1
  34. package/dist/lib/routes.js +124 -80
  35. package/dist/lib/scripts/compiler.d.ts +24 -1
  36. package/dist/lib/scripts/compiler.d.ts.map +1 -1
  37. package/dist/lib/scripts/compiler.js +183 -29
  38. package/dist/lib/scripts/conditions.d.ts +2 -0
  39. package/dist/lib/scripts/conditions.d.ts.map +1 -0
  40. package/dist/lib/scripts/conditions.js +1 -0
  41. package/dist/lib/scripts/emit.d.ts +3 -0
  42. package/dist/lib/scripts/emit.d.ts.map +1 -1
  43. package/dist/lib/scripts/emit.js +24 -5
  44. package/dist/lib/scripts/resolve.d.ts +4 -0
  45. package/dist/lib/scripts/resolve.d.ts.map +1 -1
  46. package/dist/lib/scripts/resolve.js +75 -10
  47. package/dist/lib/scripts/transform.d.ts +9 -0
  48. package/dist/lib/scripts/transform.d.ts.map +1 -1
  49. package/dist/lib/scripts/transform.js +222 -18
  50. package/dist/lib/source-maps.js +1 -1
  51. package/dist/lib/styles/compiler.d.ts +14 -1
  52. package/dist/lib/styles/compiler.d.ts.map +1 -1
  53. package/dist/lib/styles/compiler.js +78 -14
  54. package/dist/lib/styles/emit.js +4 -4
  55. package/dist/lib/styles/resolve.d.ts.map +1 -1
  56. package/dist/lib/styles/resolve.js +16 -15
  57. package/dist/lib/styles/transform.js +5 -5
  58. package/dist/lib/target.d.ts +1 -1
  59. package/dist/lib/target.d.ts.map +1 -1
  60. package/dist/lib/watch.js +1 -1
  61. package/dist/types/hmr.d.ts +36 -0
  62. package/package.json +16 -11
  63. package/src/assets.ts +4 -1
  64. package/src/lib/access.ts +445 -11
  65. package/src/lib/asset-server.ts +466 -27
  66. package/src/lib/compilation-error.ts +4 -3
  67. package/src/lib/files/compiler.ts +9 -5
  68. package/src/lib/hmr.ts +397 -0
  69. package/src/lib/injected-packages.ts +52 -16
  70. package/src/lib/inspection.ts +229 -0
  71. package/src/lib/loaders.ts +80 -0
  72. package/src/lib/module-store.ts +190 -9
  73. package/src/lib/routes.ts +158 -126
  74. package/src/lib/scripts/compiler.ts +248 -24
  75. package/src/lib/scripts/conditions.ts +1 -0
  76. package/src/lib/scripts/emit.ts +50 -5
  77. package/src/lib/scripts/resolve.ts +129 -7
  78. package/src/lib/scripts/transform.ts +290 -19
  79. package/src/lib/styles/compiler.ts +108 -8
  80. package/src/lib/styles/emit.ts +1 -1
  81. package/src/lib/styles/resolve.ts +19 -15
  82. package/src/lib/styles/transform.ts +2 -2
  83. package/src/types/hmr.d.ts +36 -0
package/README.md CHANGED
@@ -6,12 +6,14 @@ 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
- - **Access Control** - Control exactly which files can be served with allow and deny rules
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
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
 
@@ -19,6 +21,12 @@ Fetch-based server for compiling browser assets on demand.
19
21
  npm i remix
20
22
  ```
21
23
 
24
+ The optional image transform examples also use Sharp:
25
+
26
+ ```sh
27
+ npm i sharp
28
+ ```
29
+
22
30
  ## Usage
23
31
 
24
32
  Use `createAssetServer` to serve browser assets from a URL namespace in your app.
@@ -29,11 +37,8 @@ import { createAssetServer } from 'remix/assets'
29
37
 
30
38
  let assetServer = createAssetServer({
31
39
  basePath: '/assets',
32
- fileMap: {
33
- '/app/*path': 'app/*path',
34
- '/npm/*path': 'node_modules/*path',
35
- },
36
- allow: ['app/assets/**', 'node_modules/**'],
40
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
41
+ allowPackages: ['remix'],
37
42
  files: {
38
43
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
39
44
  },
@@ -46,7 +51,65 @@ router.get('/assets/*', ({ request }) => {
46
51
  })
47
52
  ```
48
53
 
49
- This example gives you an `/assets/*` endpoint that serves compiled browser assets from `app/assets` and `node_modules`.
54
+ This example gives you an `/assets/*` endpoint that serves compiled browser source from `public/` directories throughout `app/` and from the `remix` package.
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.
50
113
 
51
114
  ## Root Directory
52
115
 
@@ -59,50 +122,48 @@ import { createAssetServer } from 'remix/assets'
59
122
  let assetServer = createAssetServer({
60
123
  rootDir: path.resolve(import.meta.dirname, '..'),
61
124
  basePath: '/assets',
62
- fileMap: {
63
- '/app/*path': 'app/*path',
64
- '/npm/*path': 'node_modules/*path',
65
- },
66
- allow: ['app/assets/**', 'node_modules/**'],
125
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
126
+ allowPackages: ['remix'],
67
127
  })
68
128
  ```
69
129
 
70
130
  ## Access Control
71
131
 
72
- You must provide an `allow` list to specify which files are allowed to be served. `deny` is optional and takes precedence over `allow`.
132
+ 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
133
 
74
134
  ```ts
75
135
  import { createAssetServer } from 'remix/assets'
76
136
 
77
137
  let assetServer = createAssetServer({
78
138
  basePath: '/assets',
79
- fileMap: { '/app/*path': 'app/*path' },
80
- allow: ['app/assets/**'],
81
- deny: ['app/**/*.server.*'],
139
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
140
+ allowPackages: ['remix'],
141
+ denyFiles: ['app/**/*.server.*'],
82
142
  })
83
143
  ```
84
144
 
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.
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.
146
+
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`.
86
148
 
87
- ## File Map
149
+ ## Mounts
88
150
 
89
- 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`.
90
152
 
91
153
  ```ts
92
154
  import { createAssetServer } from 'remix/assets'
93
155
 
94
156
  let assetServer = createAssetServer({
95
157
  basePath: '/assets',
96
- fileMap: {
97
- '/app/*path': 'app/*path',
98
- '/packages/*path': '../packages/*path',
158
+ mounts: {
159
+ source: 'app',
160
+ vendor: 'node_modules',
99
161
  },
100
- allow: ['app/assets/**', '../packages/**'],
162
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
163
+ allowPackages: ['remix'],
101
164
  })
102
165
  ```
103
166
 
104
- `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`.
105
-
106
167
  ### File watching
107
168
 
108
169
  The file system is watched by default so source changes are picked up without requiring a server restart.
@@ -112,8 +173,8 @@ import { createAssetServer } from 'remix/assets'
112
173
 
113
174
  let assetServer = createAssetServer({
114
175
  basePath: '/assets',
115
- fileMap: { '/app/*path': 'app/*path' },
116
- allow: ['app/assets/**', 'app/node_modules/**'],
176
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
177
+ allowPackages: ['remix'],
117
178
  })
118
179
  ```
119
180
 
@@ -130,8 +191,8 @@ import { createAssetServer } from 'remix/assets'
130
191
 
131
192
  let assetServer = createAssetServer({
132
193
  basePath: '/assets',
133
- fileMap: { '/app/*path': 'app/*path' },
134
- allow: ['app/assets/**', 'app/node_modules/**'],
194
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
195
+ allowPackages: ['remix'],
135
196
  watch: false,
136
197
  })
137
198
  ```
@@ -143,8 +204,8 @@ import { createAssetServer } from 'remix/assets'
143
204
 
144
205
  let assetServer = createAssetServer({
145
206
  basePath: '/assets',
146
- fileMap: { '/app/*path': 'app/*path' },
147
- allow: ['app/assets/**', 'app/node_modules/**'],
207
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
208
+ allowPackages: ['remix'],
148
209
  watch: {
149
210
  ignore: ['**/node_modules/**'],
150
211
  },
@@ -156,17 +217,31 @@ let assetServer = createAssetServer({
156
217
  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
218
 
158
219
  ```ts
159
- let src = await assetServer.getHref('app/assets/entry.tsx')
160
- // '/assets/app/assets/entry.tsx'
220
+ let src = await assetServer.getHref('app/actions/public/entry.ts')
221
+ // '/assets/app/actions/public/entry.ts'
222
+ ```
223
+
224
+ ## Inspection
225
+
226
+ Use `getAssets()` for a sorted list of files that are currently browser-reachable through the
227
+ asset server. Use `getAssetDetails()` with a public URL or file path to inspect its mapping, file
228
+ type, access rules, and reachability status.
229
+
230
+ ```ts
231
+ let assets = await assetServer.getAssets()
232
+ // [{ url: '/assets/app/actions/public/entry.ts', filePath: '/project/app/actions/public/entry.ts', ... }]
233
+
234
+ let details = await assetServer.getAssetDetails('/assets/app/actions/public/entry.ts')
235
+ // { status: 'reachable', type: 'script', ... }
161
236
  ```
162
237
 
163
238
  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
239
 
165
240
  ```ts
166
- let src = await assetServer.getHref('app/assets/image.png', {
241
+ let src = await assetServer.getHref('app/media/public/image.png', {
167
242
  transform: [['resize', '100x100'], 'webp'],
168
243
  })
169
- // '/assets/app/assets/image.png?transform=resize%3A100x100&transform=webp'
244
+ // '/assets/app/media/public/image.png?transform=resize%3A100x100&transform=webp'
170
245
  ```
171
246
 
172
247
  ## Preloads
@@ -174,11 +249,14 @@ let src = await assetServer.getHref('app/assets/image.png', {
174
249
  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
250
 
176
251
  ```ts
177
- let preloads = await assetServer.getPreloads(['app/assets/entry.tsx', 'app/assets/search.tsx'])
252
+ let preloads = await assetServer.getPreloads([
253
+ 'app/actions/public/entry.ts',
254
+ 'app/search/public/search.tsx',
255
+ ])
178
256
  // [
179
- // '/assets/app/assets/entry.tsx',
180
- // '/assets/app/assets/search.tsx',
181
- // '/assets/app/assets/utils.ts',
257
+ // '/assets/app/actions/public/entry.ts',
258
+ // '/assets/app/search/public/search.tsx',
259
+ // '/assets/app/search/public/utils.ts',
182
260
  // '/assets/npm/remix/ui/index.js',
183
261
  // ...etc
184
262
  // ]
@@ -195,8 +273,8 @@ import { createAssetServer } from 'remix/assets'
195
273
 
196
274
  let assetServer = createAssetServer({
197
275
  basePath: '/assets',
198
- fileMap: { '/app/*path': 'app/*path' },
199
- allow: ['app/assets/**'],
276
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
277
+ allowPackages: ['remix'],
200
278
  watch: false,
201
279
  fingerprint: {
202
280
  buildId: process.env.GITHUB_SHA,
@@ -217,8 +295,8 @@ import { createAssetServer } from 'remix/assets'
217
295
 
218
296
  let assetServer = createAssetServer({
219
297
  basePath: '/assets',
220
- fileMap: { '/app/*path': 'app/*path' },
221
- allow: ['app/assets/**'],
298
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
299
+ allowPackages: ['remix'],
222
300
  target: {
223
301
  chrome: '109',
224
302
  ios: '15.6',
@@ -238,8 +316,8 @@ import { createAssetServer } from 'remix/assets'
238
316
 
239
317
  let assetServer = createAssetServer({
240
318
  basePath: '/assets',
241
- fileMap: { '/app/*path': 'app/*path' },
242
- allow: ['app/assets/**'],
319
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
320
+ allowPackages: ['remix'],
243
321
  sourceMaps: 'external',
244
322
  })
245
323
  ```
@@ -251,8 +329,8 @@ import { createAssetServer } from 'remix/assets'
251
329
 
252
330
  let assetServer = createAssetServer({
253
331
  basePath: '/assets',
254
- fileMap: { '/app/*path': 'app/*path' },
255
- allow: ['app/assets/**'],
332
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
333
+ allowPackages: ['remix'],
256
334
  sourceMaps: 'inline',
257
335
  sourceMapSourcePaths: 'absolute',
258
336
  })
@@ -267,8 +345,8 @@ import { createAssetServer } from 'remix/assets'
267
345
 
268
346
  let assetServer = createAssetServer({
269
347
  basePath: '/assets',
270
- fileMap: { '/app/*path': 'app/*path' },
271
- allow: ['app/assets/**'],
348
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
349
+ allowPackages: ['remix'],
272
350
  minify: true,
273
351
  })
274
352
  ```
@@ -284,8 +362,8 @@ import { createAssetServer } from 'remix/assets'
284
362
 
285
363
  let assetServer = createAssetServer({
286
364
  basePath: '/assets',
287
- fileMap: { '/app/*path': 'app/*path' },
288
- allow: ['app/assets/**', 'app/node_modules/**'],
365
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
366
+ allowPackages: ['remix'],
289
367
  scripts: {
290
368
  define: {
291
369
  'process.env.NODE_ENV': '"production"',
@@ -305,14 +383,41 @@ import { createAssetServer } from 'remix/assets'
305
383
 
306
384
  let assetServer = createAssetServer({
307
385
  basePath: '/assets',
308
- fileMap: { '/app/*path': 'app/*path' },
309
- allow: ['app/assets/**'],
386
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
387
+ allowPackages: ['remix'],
310
388
  scripts: {
311
389
  external: ['my-external-import'],
312
390
  },
313
391
  })
314
392
  ```
315
393
 
394
+ ### Loaders
395
+
396
+ 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).
397
+
398
+ ```ts
399
+ import { createAssetServer } from 'remix/assets'
400
+
401
+ let assetServer = createAssetServer({
402
+ basePath: '/assets',
403
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
404
+ denyFiles: ['app/**/*.test.*'],
405
+ scripts: {
406
+ loaders: [
407
+ (url, context, nextLoad) => {
408
+ let result = nextLoad(url, context)
409
+ return {
410
+ ...result,
411
+ source: `${result.source}\nconsole.log('loaded')`,
412
+ }
413
+ },
414
+ ],
415
+ },
416
+ })
417
+ ```
418
+
419
+ 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.
420
+
316
421
  ## File Options
317
422
 
318
423
  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 +427,8 @@ import { createAssetServer } from 'remix/assets'
322
427
 
323
428
  let assetServer = createAssetServer({
324
429
  basePath: '/assets',
325
- fileMap: { '/app/*path': 'app/*path' },
326
- allow: ['app/assets/**'],
430
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
431
+ allowPackages: ['remix'],
327
432
  files: {
328
433
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
329
434
  },
@@ -344,8 +449,8 @@ import sharp from 'sharp'
344
449
 
345
450
  let assetServer = createAssetServer({
346
451
  basePath: '/assets',
347
- fileMap: { '/app/*path': 'app/*path' },
348
- allow: ['app/assets/**'],
452
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
453
+ allowPackages: ['remix'],
349
454
  files: {
350
455
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
351
456
  transforms: {
@@ -362,7 +467,7 @@ let assetServer = createAssetServer({
362
467
  },
363
468
  })
364
469
 
365
- let imageUrl = await assetServer.getHref('app/assets/photo.jpg', {
470
+ let imageUrl = await assetServer.getHref('app/media/public/photo.jpg', {
366
471
  transform: ['webp'],
367
472
  })
368
473
  ```
@@ -374,8 +479,8 @@ import { createAssetServer, defineFileTransform } from 'remix/assets'
374
479
 
375
480
  let assetServer = createAssetServer({
376
481
  basePath: '/assets',
377
- fileMap: { '/app/*path': 'app/*path' },
378
- allow: ['app/assets/**'],
482
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
483
+ allowPackages: ['remix'],
379
484
  files: {
380
485
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
381
486
  transforms: {
@@ -395,7 +500,7 @@ let assetServer = createAssetServer({
395
500
  },
396
501
  })
397
502
 
398
- let imageUrl = await assetServer.getHref('app/assets/logo.svg', {
503
+ let imageUrl = await assetServer.getHref('app/media/public/logo.svg', {
399
504
  transform: [['recolor', '0000ff']],
400
505
  })
401
506
  ```
@@ -404,7 +509,7 @@ Hand-authored URLs use repeated `transform` search params with `name` or `name:p
404
509
 
405
510
  ```css
406
511
  .selector {
407
- background-image: url('/assets/app/assets/image.png?transform=resize:100x100&transform=webp');
512
+ background-image: url('/assets/app/media/public/image.png?transform=resize:100x100&transform=webp');
408
513
  }
409
514
  ```
410
515
 
@@ -418,8 +523,8 @@ import { optimize as optimizeSvg } from 'svgo'
418
523
 
419
524
  let assetServer = createAssetServer({
420
525
  basePath: '/assets',
421
- fileMap: { '/app/*path': 'app/*path' },
422
- allow: ['app/assets/**'],
526
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
527
+ allowPackages: ['remix'],
423
528
  files: {
424
529
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
425
530
  globalTransforms: [
@@ -450,8 +555,8 @@ import { createFsFileStorage } from 'remix/file-storage/fs'
450
555
 
451
556
  let assetServer = createAssetServer({
452
557
  basePath: '/assets',
453
- fileMap: { '/app/*path': 'app/*path' },
454
- allow: ['app/assets/**'],
558
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
559
+ allowPackages: ['remix'],
455
560
  files: {
456
561
  cache: createFsFileStorage(path.resolve('.tmp/assets-cache')),
457
562
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
@@ -471,8 +576,8 @@ import { createAssetServer } from 'remix/assets'
471
576
 
472
577
  let assetServer = createAssetServer({
473
578
  basePath: '/assets',
474
- fileMap: { '/app/*path': 'app/*path' },
475
- allow: ['app/assets/**'],
579
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
580
+ allowPackages: ['remix'],
476
581
  files: {
477
582
  maxRequestTransforms: 5,
478
583
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
@@ -518,8 +623,8 @@ import { createAssetServer } from 'remix/assets'
518
623
 
519
624
  let assetServer = createAssetServer({
520
625
  basePath: '/assets',
521
- fileMap: { '/app/*path': 'app/*path' },
522
- allow: ['app/assets/**'],
626
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
627
+ allowPackages: ['remix'],
523
628
  onError(error) {
524
629
  console.error('Failed to build client assets', error)
525
630
  return new Response('Client asset build failed', { status: 500 })
@@ -529,10 +634,164 @@ let assetServer = createAssetServer({
529
634
 
530
635
  If `onError` returns nothing, the asset server responds with the default `500 Internal Server Error` response.
531
636
 
637
+ ## Hot Module Reloading
638
+
639
+ 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.
640
+
641
+ 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):
642
+
643
+ ```ts
644
+ import { createAssetServer } from 'remix/assets'
645
+
646
+ let isDevelopment = process.env.NODE_ENV === 'development'
647
+ let assetServer = createAssetServer({
648
+ basePath: '/assets',
649
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
650
+ denyFiles: ['app/**/*.test.*'],
651
+ hmr: isDevelopment
652
+ ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
653
+ : undefined,
654
+ watch: isDevelopment,
655
+ })
656
+ ```
657
+
658
+ ### `import.meta.hot`
659
+
660
+ 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.
661
+
662
+ To type `import.meta.hot`, add the HMR types to your TypeScript config:
663
+
664
+ ```json
665
+ {
666
+ "compilerOptions": {
667
+ "types": ["remix/assets/types/hmr"]
668
+ }
669
+ }
670
+ ```
671
+
672
+ 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.
673
+
674
+ ```ts
675
+ if (import.meta.hot) {
676
+ import.meta.hot.accept()
677
+ }
678
+ ```
679
+
680
+ ### Accepting updates
681
+
682
+ 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.
683
+
684
+ ```ts
685
+ export let value = 1
686
+
687
+ if (import.meta.hot) {
688
+ import.meta.hot.accept((module) => {
689
+ if (typeof module.value !== 'number') {
690
+ import.meta.hot?.invalidate('Updated module no longer exports value')
691
+ return
692
+ }
693
+
694
+ value = module.value
695
+ })
696
+ }
697
+ ```
698
+
699
+ You can also accept updates from direct dependencies.
700
+
701
+ ```ts
702
+ import { value } from './value.ts'
703
+
704
+ let currentValue = value
705
+
706
+ export function readValue() {
707
+ return currentValue
708
+ }
709
+
710
+ if (import.meta.hot) {
711
+ import.meta.hot.accept('./value.ts', (module) => {
712
+ if (typeof module.value !== 'number') {
713
+ import.meta.hot?.invalidate('Updated dependency no longer exports value')
714
+ return
715
+ }
716
+
717
+ currentValue = module.value
718
+ })
719
+ }
720
+ ```
721
+
722
+ Multiple dependencies can be accepted at once. The callback receives an array where only the changed dependency is defined.
723
+
724
+ ```ts
725
+ if (import.meta.hot) {
726
+ import.meta.hot.accept(['./one.ts', './two.ts'], ([oneModule, twoModule]) => {
727
+ // oneModule is defined when ./one.ts changed.
728
+ // twoModule is defined when ./two.ts changed.
729
+ })
730
+ }
731
+ ```
732
+
733
+ ### Cleaning up
734
+
735
+ Register cleanup that should run before the module is replaced or disposed.
736
+
737
+ ```ts
738
+ let interval = setInterval(refreshCache, 30_000)
739
+
740
+ if (import.meta.hot) {
741
+ import.meta.hot.dispose(() => {
742
+ clearInterval(interval)
743
+ })
744
+ }
745
+ ```
746
+
747
+ The `data` object is preserved across updates for the same module. Use it for small pieces of state.
748
+
749
+ ```ts
750
+ let count = Number(import.meta.hot?.data.count ?? 0)
751
+
752
+ export function increment() {
753
+ count++
754
+ }
755
+
756
+ if (import.meta.hot) {
757
+ import.meta.hot.dispose((data) => {
758
+ data.count = count
759
+ })
760
+ }
761
+ ```
762
+
763
+ ### Invalidating updates
764
+
765
+ Call `invalidate()` inside an accept callback when the update cannot be applied safely. If no other boundary accepts the update, the browser reloads.
766
+
767
+ ```ts
768
+ if (import.meta.hot) {
769
+ import.meta.hot.accept((module) => {
770
+ if (typeof module.value !== 'number') {
771
+ import.meta.hot?.invalidate('Updated module no longer exports value')
772
+ return
773
+ }
774
+ })
775
+ }
776
+ ```
777
+
778
+ ### Server update events
779
+
780
+ When the browser HMR channel comes from `remix/node-hmr/runtime`, server updates are sent to browser modules as `server:update` events.
781
+
782
+ ```ts
783
+ if (import.meta.hot) {
784
+ import.meta.hot.on('server:update', () => {
785
+ window.location.reload()
786
+ })
787
+ }
788
+ ```
789
+
532
790
  ## Related Packages
533
791
 
534
792
  - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - A Fetch-based router that pairs naturally with `assets`
535
- - [`route-pattern`](https://github.com/remix-run/remix/tree/main/packages/route-pattern) - Route-pattern syntax for URL and route file matching
793
+ - [`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`
794
+ - [`ui-hmr`](https://github.com/remix-run/remix/tree/main/packages/ui-hmr) - Provides a Remix UI component HMR loader for `scripts.loaders`
536
795
 
537
796
  ## License
538
797
 
package/dist/assets.d.ts CHANGED
@@ -1,4 +1,7 @@
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 { AssetAccessDetails, AssetAccessRule } from './lib/access.ts';
4
+ export type { AssetServer, AssetServerOptions, BrowserHmrChannel } from './lib/asset-server.ts';
5
+ export type { AssetDetails, AssetKind, AssetStatus } from './lib/inspection.ts';
6
+ export type { ModuleLoader } from './lib/loaders.ts';
4
7
  //# 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,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC1E,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC/F,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC/E,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';