@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.
- package/README.md +331 -72
- package/dist/assets.d.ts +4 -1
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +2 -2
- package/dist/lib/access.d.ts +35 -4
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +332 -11
- package/dist/lib/asset-server.d.ts +138 -11
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +281 -30
- package/dist/lib/compilation-error.d.ts +1 -1
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/file-matcher.js +1 -1
- package/dist/lib/files/compiler.d.ts.map +1 -1
- package/dist/lib/files/compiler.js +9 -8
- package/dist/lib/files/config.js +1 -1
- package/dist/lib/hmr.d.ts +37 -0
- package/dist/lib/hmr.d.ts.map +1 -0
- package/dist/lib/hmr.js +357 -0
- package/dist/lib/injected-packages.d.ts +3 -2
- package/dist/lib/injected-packages.d.ts.map +1 -1
- package/dist/lib/injected-packages.js +43 -17
- package/dist/lib/inspection.d.ts +39 -0
- package/dist/lib/inspection.d.ts.map +1 -0
- package/dist/lib/inspection.js +160 -0
- package/dist/lib/loaders.d.ts +57 -0
- package/dist/lib/loaders.d.ts.map +1 -0
- package/dist/lib/loaders.js +1 -0
- package/dist/lib/module-store.d.ts +24 -0
- package/dist/lib/module-store.d.ts.map +1 -1
- package/dist/lib/module-store.js +136 -11
- package/dist/lib/routes.d.ts +11 -3
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +124 -80
- package/dist/lib/scripts/compiler.d.ts +24 -1
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +183 -29
- package/dist/lib/scripts/conditions.d.ts +2 -0
- package/dist/lib/scripts/conditions.d.ts.map +1 -0
- package/dist/lib/scripts/conditions.js +1 -0
- package/dist/lib/scripts/emit.d.ts +3 -0
- package/dist/lib/scripts/emit.d.ts.map +1 -1
- package/dist/lib/scripts/emit.js +24 -5
- package/dist/lib/scripts/resolve.d.ts +4 -0
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +75 -10
- package/dist/lib/scripts/transform.d.ts +9 -0
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +222 -18
- package/dist/lib/source-maps.js +1 -1
- package/dist/lib/styles/compiler.d.ts +14 -1
- package/dist/lib/styles/compiler.d.ts.map +1 -1
- package/dist/lib/styles/compiler.js +78 -14
- package/dist/lib/styles/emit.js +4 -4
- package/dist/lib/styles/resolve.d.ts.map +1 -1
- package/dist/lib/styles/resolve.js +16 -15
- package/dist/lib/styles/transform.js +5 -5
- package/dist/lib/target.d.ts +1 -1
- package/dist/lib/target.d.ts.map +1 -1
- package/dist/lib/watch.js +1 -1
- package/dist/types/hmr.d.ts +36 -0
- package/package.json +16 -11
- package/src/assets.ts +4 -1
- package/src/lib/access.ts +445 -11
- package/src/lib/asset-server.ts +466 -27
- package/src/lib/compilation-error.ts +4 -3
- package/src/lib/files/compiler.ts +9 -5
- package/src/lib/hmr.ts +397 -0
- package/src/lib/injected-packages.ts +52 -16
- package/src/lib/inspection.ts +229 -0
- package/src/lib/loaders.ts +80 -0
- package/src/lib/module-store.ts +190 -9
- package/src/lib/routes.ts +158 -126
- package/src/lib/scripts/compiler.ts +248 -24
- package/src/lib/scripts/conditions.ts +1 -0
- package/src/lib/scripts/emit.ts +50 -5
- package/src/lib/scripts/resolve.ts +129 -7
- package/src/lib/scripts/transform.ts +290 -19
- package/src/lib/styles/compiler.ts +108 -8
- package/src/lib/styles/emit.ts +1 -1
- package/src/lib/styles/resolve.ts +19 -15
- package/src/lib/styles/transform.ts +2 -2
- 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
|
-
- **
|
|
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
|
-
|
|
33
|
-
|
|
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
|
|
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
|
-
|
|
63
|
-
|
|
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 `
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
139
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
140
|
+
allowPackages: ['remix'],
|
|
141
|
+
denyFiles: ['app/**/*.server.*'],
|
|
82
142
|
})
|
|
83
143
|
```
|
|
84
144
|
|
|
85
|
-
|
|
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
|
-
##
|
|
149
|
+
## Mounts
|
|
88
150
|
|
|
89
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
158
|
+
mounts: {
|
|
159
|
+
source: 'app',
|
|
160
|
+
vendor: 'node_modules',
|
|
99
161
|
},
|
|
100
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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/
|
|
160
|
-
// '/assets/app/
|
|
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/
|
|
241
|
+
let src = await assetServer.getHref('app/media/public/image.png', {
|
|
167
242
|
transform: [['resize', '100x100'], 'webp'],
|
|
168
243
|
})
|
|
169
|
-
// '/assets/app/
|
|
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([
|
|
252
|
+
let preloads = await assetServer.getPreloads([
|
|
253
|
+
'app/actions/public/entry.ts',
|
|
254
|
+
'app/search/public/search.tsx',
|
|
255
|
+
])
|
|
178
256
|
// [
|
|
179
|
-
// '/assets/app/
|
|
180
|
-
// '/assets/app/
|
|
181
|
-
// '/assets/app/
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
288
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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
|
-
|
|
326
|
-
|
|
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
|
-
|
|
348
|
-
|
|
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/
|
|
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
|
-
|
|
378
|
-
|
|
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/
|
|
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/
|
|
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
|
-
|
|
422
|
-
|
|
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
|
-
|
|
454
|
-
|
|
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
|
-
|
|
475
|
-
|
|
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
|
-
|
|
522
|
-
|
|
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
|
-
- [`
|
|
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 {
|
|
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
|
package/dist/assets.d.ts.map
CHANGED
|
@@ -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
|
|
2
|
-
export { defineFileTransform } from
|
|
1
|
+
export { createAssetServer } from './lib/asset-server.js';
|
|
2
|
+
export { defineFileTransform } from './lib/files/config.js';
|