@remix-run/assets 0.5.0 → 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 +85 -92
- package/dist/assets.d.ts +2 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/lib/access.d.ts +29 -2
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +52 -26
- package/dist/lib/asset-server.d.ts +21 -7
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +30 -15
- package/dist/lib/compilation-error.d.ts +1 -1
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/files/compiler.js +2 -2
- 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 +11 -8
- 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/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.js +2 -2
- package/dist/lib/scripts/resolve.js +9 -9
- package/dist/lib/scripts/transform.js +2 -2
- package/dist/lib/styles/resolve.js +8 -8
- package/dist/lib/styles/transform.js +2 -2
- package/package.json +5 -6
- package/src/assets.ts +2 -0
- package/src/lib/access.ts +83 -30
- package/src/lib/asset-server.ts +50 -22
- package/src/lib/compilation-error.ts +3 -3
- package/src/lib/files/compiler.ts +2 -2
- package/src/lib/injected-packages.ts +16 -10
- package/src/lib/inspection.ts +229 -0
- package/src/lib/routes.ts +158 -126
- package/src/lib/scripts/compiler.ts +2 -2
- package/src/lib/scripts/resolve.ts +9 -9
- package/src/lib/scripts/transform.ts +2 -2
- package/src/lib/styles/resolve.ts +8 -8
- package/src/lib/styles/transform.ts +2 -2
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@ Fetch-based server for compiling browser assets on demand.
|
|
|
6
6
|
|
|
7
7
|
- **On-Demand Compilation** - Compile browser scripts and styles on demand
|
|
8
8
|
- **File Serving** - Serve configured file assets like images and fonts with optional transforms
|
|
9
|
-
- **Custom File Mapping** - Define patterns for mapping public URLs to file paths on disk
|
|
10
9
|
- **Access Control** - Control exactly which files and packages can be served
|
|
11
10
|
- **Preloads** - Generate preload URLs for scripts and styles based on imports
|
|
11
|
+
- **Inspection** - List browser-reachable assets and explain URL-to-file mappings
|
|
12
12
|
- **Caching** - Conservative caching by default with stable URLs, ETags, and revalidation
|
|
13
13
|
- **Optional Fingerprinting** - Source-based fingerprinted URLs for long-lived browser caching
|
|
14
14
|
- **Source Maps** - Serve inline or external sourcemaps
|
|
@@ -21,6 +21,12 @@ Fetch-based server for compiling browser assets on demand.
|
|
|
21
21
|
npm i remix
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
The optional image transform examples also use Sharp:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm i sharp
|
|
28
|
+
```
|
|
29
|
+
|
|
24
30
|
## Usage
|
|
25
31
|
|
|
26
32
|
Use `createAssetServer` to serve browser assets from a URL namespace in your app.
|
|
@@ -31,10 +37,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
31
37
|
|
|
32
38
|
let assetServer = createAssetServer({
|
|
33
39
|
basePath: '/assets',
|
|
34
|
-
fileMap: {
|
|
35
|
-
'/app/*path': 'app/*path',
|
|
36
|
-
'/npm/*path': 'node_modules/*path',
|
|
37
|
-
},
|
|
38
40
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
39
41
|
allowPackages: ['remix'],
|
|
40
42
|
files: {
|
|
@@ -51,6 +53,64 @@ router.get('/assets/*', ({ request }) => {
|
|
|
51
53
|
|
|
52
54
|
This example gives you an `/assets/*` endpoint that serves compiled browser source from `public/` directories throughout `app/` and from the `remix` package.
|
|
53
55
|
|
|
56
|
+
## Shared Configuration
|
|
57
|
+
|
|
58
|
+
Keep JSON-compatible asset mapping, access, and file-type settings in `remix.json` so the running
|
|
59
|
+
server and Remix CLI use the same configuration:
|
|
60
|
+
|
|
61
|
+
```jsonc
|
|
62
|
+
{
|
|
63
|
+
"$schema": "./node_modules/remix/schema/remix.json",
|
|
64
|
+
"assets": {
|
|
65
|
+
"basePath": "/assets",
|
|
66
|
+
"mounts": {
|
|
67
|
+
"app": "app",
|
|
68
|
+
"npm": "node_modules",
|
|
69
|
+
},
|
|
70
|
+
"allowFiles": ["app/routes.ts", "app/**/public/**"],
|
|
71
|
+
"allowPackages": ["remix"],
|
|
72
|
+
"denyFiles": ["app/**/*.test.*"],
|
|
73
|
+
"files": {
|
|
74
|
+
"extensions": [".svg", ".png", ".jpg", ".woff2"],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Load it from application code and add runtime-only behavior there:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { createAssetServer, defineFileTransform } from 'remix/assets'
|
|
84
|
+
import { loadConfig } from 'remix/cli'
|
|
85
|
+
import sharp from 'sharp'
|
|
86
|
+
|
|
87
|
+
let config = await loadConfig(import.meta.dirname)
|
|
88
|
+
if (config.assets === undefined) throw new Error('Missing assets configuration')
|
|
89
|
+
if (config.assets.files === undefined) throw new Error('Missing asset file configuration')
|
|
90
|
+
|
|
91
|
+
let assetServer = createAssetServer({
|
|
92
|
+
...config.assets,
|
|
93
|
+
files: {
|
|
94
|
+
...config.assets.files,
|
|
95
|
+
transforms: {
|
|
96
|
+
webp: defineFileTransform({
|
|
97
|
+
extensions: ['.png', '.jpg'],
|
|
98
|
+
async transform(bytes) {
|
|
99
|
+
return {
|
|
100
|
+
content: await sharp(bytes).webp({ quality: 80 }).toBuffer(),
|
|
101
|
+
extension: '.webp',
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`loadConfig()` accepts either a config file or a directory. When given a directory, it searches
|
|
111
|
+
upward for the nearest `remix.json`. Run `remix assets` to list reachable files, or
|
|
112
|
+
`remix assets inspect <url-or-file>` to inspect one mapping and its access decision.
|
|
113
|
+
|
|
54
114
|
## Root Directory
|
|
55
115
|
|
|
56
116
|
Use `rootDir` to specify the root directory of the asset server, which is used to resolve relative file paths. Defaults to `process.cwd()`.
|
|
@@ -62,10 +122,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
62
122
|
let assetServer = createAssetServer({
|
|
63
123
|
rootDir: path.resolve(import.meta.dirname, '..'),
|
|
64
124
|
basePath: '/assets',
|
|
65
|
-
fileMap: {
|
|
66
|
-
'/app/*path': 'app/*path',
|
|
67
|
-
'/npm/*path': 'node_modules/*path',
|
|
68
|
-
},
|
|
69
125
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
70
126
|
allowPackages: ['remix'],
|
|
71
127
|
})
|
|
@@ -80,10 +136,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
80
136
|
|
|
81
137
|
let assetServer = createAssetServer({
|
|
82
138
|
basePath: '/assets',
|
|
83
|
-
fileMap: {
|
|
84
|
-
'/app/*path': 'app/*path',
|
|
85
|
-
'/npm/*path': 'node_modules/*path',
|
|
86
|
-
},
|
|
87
139
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
88
140
|
allowPackages: ['remix'],
|
|
89
141
|
denyFiles: ['app/**/*.server.*'],
|
|
@@ -92,28 +144,26 @@ let assetServer = createAssetServer({
|
|
|
92
144
|
|
|
93
145
|
Values for `allowFiles` and `denyFiles` are file paths or globs. Relative values are resolved from `rootDir`. Absolute file paths match exactly, and absolute directory paths also match their descendants.
|
|
94
146
|
|
|
95
|
-
Values for `allowPackages` are exact package names. Dependencies and installed optional dependencies of packages in `allowPackages` are also allowed automatically. Peer dependencies must be listed explicitly if they should be browser-reachable. Allowed package files must still be reachable through `
|
|
147
|
+
Values for `allowPackages` are exact package names. Dependencies and installed optional dependencies of packages in `allowPackages` are also allowed automatically. Peer dependencies must be listed explicitly if they should be browser-reachable. Allowed package files must still be reachable through `mounts`.
|
|
96
148
|
|
|
97
|
-
##
|
|
149
|
+
## Mounts
|
|
98
150
|
|
|
99
|
-
|
|
151
|
+
By default, the asset server mounts the `app` directory at `/app` and `node_modules` at `/npm`. Use `mounts` to replace these defaults. Keys are public paths relative to `basePath`, and values are directory paths relative to `rootDir`.
|
|
100
152
|
|
|
101
153
|
```ts
|
|
102
154
|
import { createAssetServer } from 'remix/assets'
|
|
103
155
|
|
|
104
156
|
let assetServer = createAssetServer({
|
|
105
157
|
basePath: '/assets',
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
158
|
+
mounts: {
|
|
159
|
+
source: 'app',
|
|
160
|
+
vendor: 'node_modules',
|
|
109
161
|
},
|
|
110
162
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
111
163
|
allowPackages: ['remix'],
|
|
112
164
|
})
|
|
113
165
|
```
|
|
114
166
|
|
|
115
|
-
`fileMap` entries use [`route-pattern`](https://github.com/remix-run/remix/tree/main/packages/route-pattern) syntax for both URL and file patterns. Wildcards must be named, and the same params must appear in both patterns so imports can be rewritten back to public URLs. For example, with `basePath: '/assets'`, a `fileMap` key of `'/app/*path'` is served at `/assets/app/*path`.
|
|
116
|
-
|
|
117
167
|
### File watching
|
|
118
168
|
|
|
119
169
|
The file system is watched by default so source changes are picked up without requiring a server restart.
|
|
@@ -123,10 +173,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
123
173
|
|
|
124
174
|
let assetServer = createAssetServer({
|
|
125
175
|
basePath: '/assets',
|
|
126
|
-
fileMap: {
|
|
127
|
-
'/app/*path': 'app/*path',
|
|
128
|
-
'/npm/*path': 'node_modules/*path',
|
|
129
|
-
},
|
|
130
176
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
131
177
|
allowPackages: ['remix'],
|
|
132
178
|
})
|
|
@@ -145,10 +191,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
145
191
|
|
|
146
192
|
let assetServer = createAssetServer({
|
|
147
193
|
basePath: '/assets',
|
|
148
|
-
fileMap: {
|
|
149
|
-
'/app/*path': 'app/*path',
|
|
150
|
-
'/npm/*path': 'node_modules/*path',
|
|
151
|
-
},
|
|
152
194
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
153
195
|
allowPackages: ['remix'],
|
|
154
196
|
watch: false,
|
|
@@ -162,10 +204,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
162
204
|
|
|
163
205
|
let assetServer = createAssetServer({
|
|
164
206
|
basePath: '/assets',
|
|
165
|
-
fileMap: {
|
|
166
|
-
'/app/*path': 'app/*path',
|
|
167
|
-
'/npm/*path': 'node_modules/*path',
|
|
168
|
-
},
|
|
169
207
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
170
208
|
allowPackages: ['remix'],
|
|
171
209
|
watch: {
|
|
@@ -183,6 +221,20 @@ let src = await assetServer.getHref('app/actions/public/entry.ts')
|
|
|
183
221
|
// '/assets/app/actions/public/entry.ts'
|
|
184
222
|
```
|
|
185
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', ... }
|
|
236
|
+
```
|
|
237
|
+
|
|
186
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.
|
|
187
239
|
|
|
188
240
|
```ts
|
|
@@ -221,10 +273,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
221
273
|
|
|
222
274
|
let assetServer = createAssetServer({
|
|
223
275
|
basePath: '/assets',
|
|
224
|
-
fileMap: {
|
|
225
|
-
'/app/*path': 'app/*path',
|
|
226
|
-
'/npm/*path': 'node_modules/*path',
|
|
227
|
-
},
|
|
228
276
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
229
277
|
allowPackages: ['remix'],
|
|
230
278
|
watch: false,
|
|
@@ -247,10 +295,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
247
295
|
|
|
248
296
|
let assetServer = createAssetServer({
|
|
249
297
|
basePath: '/assets',
|
|
250
|
-
fileMap: {
|
|
251
|
-
'/app/*path': 'app/*path',
|
|
252
|
-
'/npm/*path': 'node_modules/*path',
|
|
253
|
-
},
|
|
254
298
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
255
299
|
allowPackages: ['remix'],
|
|
256
300
|
target: {
|
|
@@ -272,10 +316,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
272
316
|
|
|
273
317
|
let assetServer = createAssetServer({
|
|
274
318
|
basePath: '/assets',
|
|
275
|
-
fileMap: {
|
|
276
|
-
'/app/*path': 'app/*path',
|
|
277
|
-
'/npm/*path': 'node_modules/*path',
|
|
278
|
-
},
|
|
279
319
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
280
320
|
allowPackages: ['remix'],
|
|
281
321
|
sourceMaps: 'external',
|
|
@@ -289,10 +329,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
289
329
|
|
|
290
330
|
let assetServer = createAssetServer({
|
|
291
331
|
basePath: '/assets',
|
|
292
|
-
fileMap: {
|
|
293
|
-
'/app/*path': 'app/*path',
|
|
294
|
-
'/npm/*path': 'node_modules/*path',
|
|
295
|
-
},
|
|
296
332
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
297
333
|
allowPackages: ['remix'],
|
|
298
334
|
sourceMaps: 'inline',
|
|
@@ -309,10 +345,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
309
345
|
|
|
310
346
|
let assetServer = createAssetServer({
|
|
311
347
|
basePath: '/assets',
|
|
312
|
-
fileMap: {
|
|
313
|
-
'/app/*path': 'app/*path',
|
|
314
|
-
'/npm/*path': 'node_modules/*path',
|
|
315
|
-
},
|
|
316
348
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
317
349
|
allowPackages: ['remix'],
|
|
318
350
|
minify: true,
|
|
@@ -330,10 +362,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
330
362
|
|
|
331
363
|
let assetServer = createAssetServer({
|
|
332
364
|
basePath: '/assets',
|
|
333
|
-
fileMap: {
|
|
334
|
-
'/app/*path': 'app/*path',
|
|
335
|
-
'/npm/*path': 'node_modules/*path',
|
|
336
|
-
},
|
|
337
365
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
338
366
|
allowPackages: ['remix'],
|
|
339
367
|
scripts: {
|
|
@@ -355,10 +383,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
355
383
|
|
|
356
384
|
let assetServer = createAssetServer({
|
|
357
385
|
basePath: '/assets',
|
|
358
|
-
fileMap: {
|
|
359
|
-
'/app/*path': 'app/*path',
|
|
360
|
-
'/npm/*path': 'node_modules/*path',
|
|
361
|
-
},
|
|
362
386
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
363
387
|
allowPackages: ['remix'],
|
|
364
388
|
scripts: {
|
|
@@ -376,7 +400,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
376
400
|
|
|
377
401
|
let assetServer = createAssetServer({
|
|
378
402
|
basePath: '/assets',
|
|
379
|
-
fileMap: { '/app/*path': 'app/*path' },
|
|
380
403
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
381
404
|
denyFiles: ['app/**/*.test.*'],
|
|
382
405
|
scripts: {
|
|
@@ -404,10 +427,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
404
427
|
|
|
405
428
|
let assetServer = createAssetServer({
|
|
406
429
|
basePath: '/assets',
|
|
407
|
-
fileMap: {
|
|
408
|
-
'/app/*path': 'app/*path',
|
|
409
|
-
'/npm/*path': 'node_modules/*path',
|
|
410
|
-
},
|
|
411
430
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
412
431
|
allowPackages: ['remix'],
|
|
413
432
|
files: {
|
|
@@ -430,10 +449,6 @@ import sharp from 'sharp'
|
|
|
430
449
|
|
|
431
450
|
let assetServer = createAssetServer({
|
|
432
451
|
basePath: '/assets',
|
|
433
|
-
fileMap: {
|
|
434
|
-
'/app/*path': 'app/*path',
|
|
435
|
-
'/npm/*path': 'node_modules/*path',
|
|
436
|
-
},
|
|
437
452
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
438
453
|
allowPackages: ['remix'],
|
|
439
454
|
files: {
|
|
@@ -464,10 +479,6 @@ import { createAssetServer, defineFileTransform } from 'remix/assets'
|
|
|
464
479
|
|
|
465
480
|
let assetServer = createAssetServer({
|
|
466
481
|
basePath: '/assets',
|
|
467
|
-
fileMap: {
|
|
468
|
-
'/app/*path': 'app/*path',
|
|
469
|
-
'/npm/*path': 'node_modules/*path',
|
|
470
|
-
},
|
|
471
482
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
472
483
|
allowPackages: ['remix'],
|
|
473
484
|
files: {
|
|
@@ -512,10 +523,6 @@ import { optimize as optimizeSvg } from 'svgo'
|
|
|
512
523
|
|
|
513
524
|
let assetServer = createAssetServer({
|
|
514
525
|
basePath: '/assets',
|
|
515
|
-
fileMap: {
|
|
516
|
-
'/app/*path': 'app/*path',
|
|
517
|
-
'/npm/*path': 'node_modules/*path',
|
|
518
|
-
},
|
|
519
526
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
520
527
|
allowPackages: ['remix'],
|
|
521
528
|
files: {
|
|
@@ -548,10 +555,6 @@ import { createFsFileStorage } from 'remix/file-storage/fs'
|
|
|
548
555
|
|
|
549
556
|
let assetServer = createAssetServer({
|
|
550
557
|
basePath: '/assets',
|
|
551
|
-
fileMap: {
|
|
552
|
-
'/app/*path': 'app/*path',
|
|
553
|
-
'/npm/*path': 'node_modules/*path',
|
|
554
|
-
},
|
|
555
558
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
556
559
|
allowPackages: ['remix'],
|
|
557
560
|
files: {
|
|
@@ -573,10 +576,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
573
576
|
|
|
574
577
|
let assetServer = createAssetServer({
|
|
575
578
|
basePath: '/assets',
|
|
576
|
-
fileMap: {
|
|
577
|
-
'/app/*path': 'app/*path',
|
|
578
|
-
'/npm/*path': 'node_modules/*path',
|
|
579
|
-
},
|
|
580
579
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
581
580
|
allowPackages: ['remix'],
|
|
582
581
|
files: {
|
|
@@ -624,10 +623,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
624
623
|
|
|
625
624
|
let assetServer = createAssetServer({
|
|
626
625
|
basePath: '/assets',
|
|
627
|
-
fileMap: {
|
|
628
|
-
'/app/*path': 'app/*path',
|
|
629
|
-
'/npm/*path': 'node_modules/*path',
|
|
630
|
-
},
|
|
631
626
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
632
627
|
allowPackages: ['remix'],
|
|
633
628
|
onError(error) {
|
|
@@ -651,7 +646,6 @@ import { createAssetServer } from 'remix/assets'
|
|
|
651
646
|
let isDevelopment = process.env.NODE_ENV === 'development'
|
|
652
647
|
let assetServer = createAssetServer({
|
|
653
648
|
basePath: '/assets',
|
|
654
|
-
fileMap: { '/app/*path': 'app/*path' },
|
|
655
649
|
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
656
650
|
denyFiles: ['app/**/*.test.*'],
|
|
657
651
|
hmr: isDevelopment
|
|
@@ -798,7 +792,6 @@ if (import.meta.hot) {
|
|
|
798
792
|
- [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - A Fetch-based router that pairs naturally with `assets`
|
|
799
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`
|
|
800
794
|
- [`ui-hmr`](https://github.com/remix-run/remix/tree/main/packages/ui-hmr) - Provides a Remix UI component HMR loader for `scripts.loaders`
|
|
801
|
-
- [`route-pattern`](https://github.com/remix-run/remix/tree/main/packages/route-pattern) - Route-pattern syntax for URL and route file matching
|
|
802
795
|
|
|
803
796
|
## License
|
|
804
797
|
|
package/dist/assets.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { createAssetServer } from './lib/asset-server.ts';
|
|
2
2
|
export { defineFileTransform } from './lib/files/config.ts';
|
|
3
|
+
export type { AssetAccessDetails, AssetAccessRule } from './lib/access.ts';
|
|
3
4
|
export type { AssetServer, AssetServerOptions, BrowserHmrChannel } from './lib/asset-server.ts';
|
|
5
|
+
export type { AssetDetails, AssetKind, AssetStatus } from './lib/inspection.ts';
|
|
4
6
|
export type { ModuleLoader } from './lib/loaders.ts';
|
|
5
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,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC/F,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC1E,YAAY,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/lib/access.d.ts
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
/** Access-policy result for an inspected asset file. */
|
|
2
|
+
export interface AssetAccessDetails {
|
|
3
|
+
/** Whether the asset server may serve the file. */
|
|
4
|
+
allowed: boolean;
|
|
5
|
+
/** The first configured rule that allowed the file, when one matched. */
|
|
6
|
+
allowedBy?: AssetAccessRule;
|
|
7
|
+
/** The first matching `denyFiles` pattern, when access was denied. */
|
|
8
|
+
deniedBy?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Rule that allows an inspected asset file to be served. */
|
|
11
|
+
export type AssetAccessRule =
|
|
12
|
+
/** A matching `allowFiles` entry. */
|
|
13
|
+
{
|
|
14
|
+
kind: 'file';
|
|
15
|
+
value: string;
|
|
16
|
+
}
|
|
17
|
+
/** A runtime file provided internally by the asset server. */
|
|
18
|
+
| {
|
|
19
|
+
kind: 'injected';
|
|
20
|
+
value: string;
|
|
21
|
+
}
|
|
22
|
+
/** A matching `allowPackages` entry. */
|
|
23
|
+
| {
|
|
24
|
+
kind: 'package';
|
|
25
|
+
value: string;
|
|
26
|
+
};
|
|
27
|
+
export type AccessPolicy = {
|
|
28
|
+
getAllowedPackageRoots(): readonly string[];
|
|
2
29
|
getPackageWatchDirectories(): readonly string[];
|
|
3
30
|
handleFileEvent(filePath: string): void;
|
|
31
|
+
inspect(filePath: string): AssetAccessDetails;
|
|
4
32
|
isAllowed(filePath: string): boolean;
|
|
5
33
|
};
|
|
6
34
|
export declare function createAccessPolicy(options: {
|
|
@@ -10,5 +38,4 @@ export declare function createAccessPolicy(options: {
|
|
|
10
38
|
packageSearchRoots?: readonly string[];
|
|
11
39
|
rootDir: string;
|
|
12
40
|
}): AccessPolicy;
|
|
13
|
-
export {};
|
|
14
41
|
//# sourceMappingURL=access.d.ts.map
|
package/dist/lib/access.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/lib/access.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG;
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/lib/access.ts"],"names":[],"mappings":"AAMA,wDAAwD;AACxD,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,OAAO,EAAE,OAAO,CAAA;IAChB,yEAAyE;IACzE,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,6DAA6D;AAC7D,MAAM,MAAM,eAAe;AACzB,qCAAqC;AACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACjC,8DAA8D;GAC5D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACrC,wCAAwC;GACtC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtC,MAAM,MAAM,YAAY,GAAG;IACzB,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAC3C,0BAA0B,IAAI,SAAS,MAAM,EAAE,CAAA;IAC/C,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAAA;IAC7C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;CACrC,CAAA;AAcD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,YAAY,CAoFf"}
|
package/dist/lib/access.js
CHANGED
|
@@ -15,9 +15,15 @@ const packageStateFileNames = new Set([
|
|
|
15
15
|
const packageManagerRootFileNames = packageStateFileNames;
|
|
16
16
|
const packageNamePartPattern = /^[A-Za-z0-9._~-]+$/;
|
|
17
17
|
export function createAccessPolicy(options) {
|
|
18
|
-
let allowMatchers = options.allowFiles.map((pattern) =>
|
|
18
|
+
let allowMatchers = options.allowFiles.map((pattern) => ({
|
|
19
|
+
matcher: createFileMatcher(pattern, options.rootDir),
|
|
20
|
+
pattern,
|
|
21
|
+
}));
|
|
19
22
|
let allowPackageNames = normalizePackageNames(options.allowPackages, 'allowPackages');
|
|
20
|
-
let denyMatchers = (options.denyFiles ?? []).map((pattern) =>
|
|
23
|
+
let denyMatchers = (options.denyFiles ?? []).map((pattern) => ({
|
|
24
|
+
matcher: createFileMatcher(pattern, options.rootDir),
|
|
25
|
+
pattern,
|
|
26
|
+
}));
|
|
21
27
|
let packageSearchRoots = [options.rootDir, ...(options.packageSearchRoots ?? [])];
|
|
22
28
|
let packageRootPaths = createPackageRootPaths({
|
|
23
29
|
allowPackageNames,
|
|
@@ -36,13 +42,40 @@ export function createAccessPolicy(options) {
|
|
|
36
42
|
allowPackageRootPathTrie = createPackageRootPathTrie(packageRootPaths);
|
|
37
43
|
packageRootsDirty = false;
|
|
38
44
|
}
|
|
39
|
-
function
|
|
45
|
+
function getAllowedPackageName(filePath) {
|
|
40
46
|
if (allowPackageNames.size === 0)
|
|
41
|
-
return
|
|
47
|
+
return undefined;
|
|
42
48
|
refreshPackageRootPathTries();
|
|
43
|
-
return
|
|
49
|
+
return getPackageNameFromRootPathTrie(filePath, allowPackageRootPathTrie);
|
|
50
|
+
}
|
|
51
|
+
function inspect(filePath) {
|
|
52
|
+
if (isInjectedPackageFilePath(filePath)) {
|
|
53
|
+
return { allowed: true, allowedBy: { kind: 'injected', value: '@remix-run/assets' } };
|
|
54
|
+
}
|
|
55
|
+
let allowedBy;
|
|
56
|
+
let allowMatch = allowMatchers.find(({ matcher }) => matcher(filePath));
|
|
57
|
+
if (allowMatch) {
|
|
58
|
+
allowedBy = { kind: 'file', value: allowMatch.pattern };
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
let packageName = getAllowedPackageName(filePath);
|
|
62
|
+
if (packageName !== undefined) {
|
|
63
|
+
allowedBy = { kind: 'package', value: packageName };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!allowedBy)
|
|
67
|
+
return { allowed: false };
|
|
68
|
+
let denyMatch = denyMatchers.find(({ matcher }) => matcher(filePath));
|
|
69
|
+
if (denyMatch) {
|
|
70
|
+
return { allowed: false, allowedBy, deniedBy: denyMatch.pattern };
|
|
71
|
+
}
|
|
72
|
+
return { allowed: true, allowedBy };
|
|
44
73
|
}
|
|
45
74
|
return {
|
|
75
|
+
getAllowedPackageRoots() {
|
|
76
|
+
refreshPackageRootPathTries();
|
|
77
|
+
return [...packageRootPaths.keys()];
|
|
78
|
+
},
|
|
46
79
|
getPackageWatchDirectories() {
|
|
47
80
|
if (allowPackageNames.size === 0)
|
|
48
81
|
return [];
|
|
@@ -55,15 +88,9 @@ export function createAccessPolicy(options) {
|
|
|
55
88
|
return;
|
|
56
89
|
packageRootsDirty = true;
|
|
57
90
|
},
|
|
91
|
+
inspect,
|
|
58
92
|
isAllowed(filePath) {
|
|
59
|
-
|
|
60
|
-
return true;
|
|
61
|
-
if (!allowMatchers.some((matcher) => matcher(filePath)) && !isAllowedPackage(filePath)) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
if (denyMatchers.length > 0 && denyMatchers.some((matcher) => matcher(filePath)))
|
|
65
|
-
return false;
|
|
66
|
-
return true;
|
|
93
|
+
return inspect(filePath).allowed;
|
|
67
94
|
},
|
|
68
95
|
};
|
|
69
96
|
}
|
|
@@ -87,7 +114,7 @@ function validatePackageName(packageName, message) {
|
|
|
87
114
|
}
|
|
88
115
|
}
|
|
89
116
|
function createPackageRootPaths(options) {
|
|
90
|
-
let allowPackageRootPaths = new
|
|
117
|
+
let allowPackageRootPaths = new Map();
|
|
91
118
|
let allowQueue = [];
|
|
92
119
|
let seenAllowedPackageRoots = new Set();
|
|
93
120
|
let searchRoots = normalizePackageSearchRoots(options.searchRoots);
|
|
@@ -105,13 +132,13 @@ function createPackageRootPaths(options) {
|
|
|
105
132
|
}
|
|
106
133
|
}
|
|
107
134
|
while (allowQueue.length > 0) {
|
|
108
|
-
let { packageJsonPath } = allowQueue.shift();
|
|
135
|
+
let { packageJsonPath, packageName } = allowQueue.shift();
|
|
109
136
|
let packageRootPath = normalizeFilePath(path.dirname(packageJsonPath));
|
|
110
137
|
if (seenAllowedPackageRoots.has(packageRootPath))
|
|
111
138
|
continue;
|
|
112
139
|
seenAllowedPackageRoots.add(packageRootPath);
|
|
113
140
|
let packageJson = readPackageJson(packageJsonPath);
|
|
114
|
-
allowPackageRootPaths.
|
|
141
|
+
allowPackageRootPaths.set(packageRootPath, packageName);
|
|
115
142
|
for (let dependencyName of Object.keys(packageJson.dependencies ?? {})) {
|
|
116
143
|
validatePackageName(dependencyName, `Dependency "${dependencyName}" from ${packageJsonPath} must be a package name.`);
|
|
117
144
|
let dependencyPackageJsonPath = findPackageJsonPath(dependencyName, packageRootPath);
|
|
@@ -138,7 +165,7 @@ function createPackageRootPaths(options) {
|
|
|
138
165
|
}
|
|
139
166
|
function createPackageRootPathTrie(packageRootPaths) {
|
|
140
167
|
let rootNode = createPackageRootPathTrieNode();
|
|
141
|
-
for (let packageRootPath of packageRootPaths) {
|
|
168
|
+
for (let [packageRootPath, packageName] of packageRootPaths) {
|
|
142
169
|
let node = rootNode;
|
|
143
170
|
for (let segment of getFilePathSegments(packageRootPath)) {
|
|
144
171
|
let childNode = node.children.get(segment);
|
|
@@ -148,29 +175,28 @@ function createPackageRootPathTrie(packageRootPaths) {
|
|
|
148
175
|
}
|
|
149
176
|
node = childNode;
|
|
150
177
|
}
|
|
151
|
-
node.
|
|
178
|
+
node.packageName = packageName;
|
|
152
179
|
}
|
|
153
180
|
return rootNode;
|
|
154
181
|
}
|
|
155
182
|
function createPackageRootPathTrieNode() {
|
|
156
183
|
return {
|
|
157
184
|
children: new Map(),
|
|
158
|
-
packageRoot: false,
|
|
159
185
|
};
|
|
160
186
|
}
|
|
161
|
-
function
|
|
187
|
+
function getPackageNameFromRootPathTrie(filePath, trie) {
|
|
162
188
|
let node = trie;
|
|
163
|
-
if (node.
|
|
164
|
-
return
|
|
189
|
+
if (node.packageName !== undefined)
|
|
190
|
+
return node.packageName;
|
|
165
191
|
for (let segment of getFilePathSegments(filePath)) {
|
|
166
192
|
let childNode = node.children.get(segment);
|
|
167
193
|
if (!childNode)
|
|
168
|
-
return
|
|
169
|
-
if (childNode.
|
|
170
|
-
return
|
|
194
|
+
return undefined;
|
|
195
|
+
if (childNode.packageName !== undefined)
|
|
196
|
+
return childNode.packageName;
|
|
171
197
|
node = childNode;
|
|
172
198
|
}
|
|
173
|
-
return
|
|
199
|
+
return undefined;
|
|
174
200
|
}
|
|
175
201
|
function getFilePathSegments(filePath) {
|
|
176
202
|
return normalizeFilePath(filePath).split('/');
|
|
@@ -3,6 +3,7 @@ import type { HmrPayload } from './hmr.ts';
|
|
|
3
3
|
import type { ModuleLoader } from './loaders.ts';
|
|
4
4
|
import type { ScriptHmrUpdate } from './scripts/compiler.ts';
|
|
5
5
|
import type { AssetTarget } from './target.ts';
|
|
6
|
+
import { type AssetDetails } from './inspection.ts';
|
|
6
7
|
import type { ChokidarWatcher } from './watch.ts';
|
|
7
8
|
interface AssetServerWatchOptions {
|
|
8
9
|
/**
|
|
@@ -135,8 +136,14 @@ interface AssetServerScriptOptions {
|
|
|
135
136
|
export interface AssetServerOptions<transforms extends AssetRequestTransformMap = {}> {
|
|
136
137
|
/** Public mount path for this asset server, e.g. `'/assets'`. */
|
|
137
138
|
basePath: string;
|
|
138
|
-
/**
|
|
139
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Directories to mount at public URL paths.
|
|
141
|
+
*
|
|
142
|
+
* Each key is a public URL path and its value is a directory relative to `rootDir`. Defaults to
|
|
143
|
+
* `{ app: 'app', npm: 'node_modules' }`. Public paths must not contain query strings, fragments,
|
|
144
|
+
* or encoded dot segments.
|
|
145
|
+
*/
|
|
146
|
+
mounts?: Readonly<Record<string, string>>;
|
|
140
147
|
/**
|
|
141
148
|
* Root directory used to resolve relative file paths. Defaults to `process.cwd()`.
|
|
142
149
|
*/
|
|
@@ -147,7 +154,7 @@ export interface AssetServerOptions<transforms extends AssetRequestTransformMap
|
|
|
147
154
|
allowFiles: readonly string[];
|
|
148
155
|
/**
|
|
149
156
|
* Exact package names whose files are allowed to be served. Dependencies and installed optional
|
|
150
|
-
* dependencies are allowed automatically. Package files must still
|
|
157
|
+
* dependencies are allowed automatically. Package files must still be within a configured mount.
|
|
151
158
|
*/
|
|
152
159
|
allowPackages?: readonly string[];
|
|
153
160
|
/**
|
|
@@ -237,6 +244,16 @@ export interface AssetServer<transforms extends AssetRequestTransformMap = {}> {
|
|
|
237
244
|
* Returns preload URLs for one or more served asset files, ordered shallowest-first.
|
|
238
245
|
*/
|
|
239
246
|
getPreloads(filePath: string | readonly string[]): Promise<string[]>;
|
|
247
|
+
/**
|
|
248
|
+
* Returns diagnostic details about one public asset URL or file path, including the matched mount
|
|
249
|
+
* roots, access rules, file type, and browser-reachability status.
|
|
250
|
+
*/
|
|
251
|
+
getAssetDetails(input: string): Promise<AssetDetails>;
|
|
252
|
+
/**
|
|
253
|
+
* Returns every file currently reachable through this asset server, sorted by public URL and
|
|
254
|
+
* then absolute file path.
|
|
255
|
+
*/
|
|
256
|
+
getAssets(): Promise<AssetDetails[]>;
|
|
240
257
|
/**
|
|
241
258
|
* Closes this server's filesystem watcher and browser HMR channel.
|
|
242
259
|
*
|
|
@@ -250,7 +267,7 @@ export declare function getInternalWatchTargets<transforms extends AssetRequestT
|
|
|
250
267
|
* Create an asset server instance
|
|
251
268
|
*
|
|
252
269
|
* Compiles TypeScript/JavaScript scripts and CSS styles on demand with optional
|
|
253
|
-
* source-based URL fingerprinting, caching, and configurable
|
|
270
|
+
* source-based URL fingerprinting, caching, and configurable directory mounts.
|
|
254
271
|
*
|
|
255
272
|
* @param options Server configuration
|
|
256
273
|
* @returns A {@link AssetServer} with `fetch()`, `getHref()`, and `getPreloads()` methods
|
|
@@ -259,9 +276,6 @@ export declare function getInternalWatchTargets<transforms extends AssetRequestT
|
|
|
259
276
|
* ```ts
|
|
260
277
|
* let assetServer = createAssetServer({
|
|
261
278
|
* basePath: '/assets',
|
|
262
|
-
* fileMap: {
|
|
263
|
-
* '/app/*path': 'app/*path',
|
|
264
|
-
* },
|
|
265
279
|
* allowFiles: ['app/routes.ts', 'app/**\/public/**'],
|
|
266
280
|
* allowPackages: ['remix'],
|
|
267
281
|
* denyFiles: ['app/**\/*.test.*'],
|