remix 3.0.0-beta.0 → 3.0.0-beta.10
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 +4 -2
- package/dist/assets/types/hmr.d.ts +2 -0
- package/dist/cli-entry.js +1 -1
- package/dist/data-table/cli.d.ts +2 -0
- package/dist/data-table/cli.d.ts.map +1 -0
- package/dist/{ui/scroll-lock.js → data-table/cli.js} +1 -1
- package/dist/fetch-router.d.ts +7 -0
- package/dist/fetch-router.d.ts.map +1 -1
- package/dist/headers/accept-encoding.d.ts +2 -0
- package/dist/headers/accept-encoding.d.ts.map +1 -0
- package/dist/headers/accept-encoding.js +2 -0
- package/dist/headers/accept-language.d.ts +2 -0
- package/dist/headers/accept-language.d.ts.map +1 -0
- package/dist/headers/accept-language.js +2 -0
- package/dist/headers/accept.d.ts +2 -0
- package/dist/headers/accept.d.ts.map +1 -0
- package/dist/headers/accept.js +2 -0
- package/dist/headers/cache-control.d.ts +2 -0
- package/dist/headers/cache-control.d.ts.map +1 -0
- package/dist/headers/cache-control.js +2 -0
- package/dist/headers/content-disposition.d.ts +2 -0
- package/dist/headers/content-disposition.d.ts.map +1 -0
- package/dist/headers/content-disposition.js +2 -0
- package/dist/headers/content-range.d.ts +2 -0
- package/dist/headers/content-range.d.ts.map +1 -0
- package/dist/headers/content-range.js +2 -0
- package/dist/headers/content-type.d.ts +2 -0
- package/dist/headers/content-type.d.ts.map +1 -0
- package/dist/headers/content-type.js +2 -0
- package/dist/headers/cookie.d.ts +2 -0
- package/dist/headers/cookie.d.ts.map +1 -0
- package/dist/headers/cookie.js +2 -0
- package/dist/headers/if-match.d.ts +2 -0
- package/dist/headers/if-match.d.ts.map +1 -0
- package/dist/headers/if-match.js +2 -0
- package/dist/headers/if-none-match.d.ts +2 -0
- package/dist/headers/if-none-match.d.ts.map +1 -0
- package/dist/headers/if-none-match.js +2 -0
- package/dist/headers/if-range.d.ts +2 -0
- package/dist/headers/if-range.d.ts.map +1 -0
- package/dist/headers/if-range.js +2 -0
- package/dist/headers/range.d.ts +2 -0
- package/dist/headers/range.d.ts.map +1 -0
- package/dist/headers/range.js +2 -0
- package/dist/headers/raw-headers.d.ts +2 -0
- package/dist/headers/raw-headers.d.ts.map +1 -0
- package/dist/headers/raw-headers.js +2 -0
- package/dist/headers/set-cookie.d.ts +2 -0
- package/dist/headers/set-cookie.d.ts.map +1 -0
- package/dist/headers/set-cookie.js +2 -0
- package/dist/headers/vary.d.ts +2 -0
- package/dist/headers/vary.d.ts.map +1 -0
- package/dist/{ui/separator.js → headers/vary.js} +1 -1
- package/dist/node-hmr/runtime.d.ts +2 -0
- package/dist/node-hmr/runtime.d.ts.map +1 -0
- package/dist/node-hmr/runtime.js +2 -0
- package/dist/node-hmr/types.d.ts +2 -0
- package/dist/node-hmr.d.ts +2 -0
- package/dist/node-hmr.d.ts.map +1 -0
- package/dist/{ui/glyph.js → node-hmr.js} +1 -1
- package/dist/node-tsx/load-module.d.ts +2 -0
- package/dist/node-tsx/load-module.d.ts.map +1 -0
- package/dist/node-tsx/load-module.js +2 -0
- package/dist/node-tsx.d.ts +3 -0
- package/dist/node-tsx.d.ts.map +1 -0
- package/dist/node-tsx.js +3 -0
- package/dist/render-middleware.d.ts +2 -0
- package/dist/render-middleware.d.ts.map +1 -0
- package/dist/render-middleware.js +2 -0
- package/dist/route-pattern/href.d.ts +2 -0
- package/dist/route-pattern/href.d.ts.map +1 -0
- package/dist/route-pattern/href.js +2 -0
- package/dist/route-pattern/join.d.ts +2 -0
- package/dist/route-pattern/join.d.ts.map +1 -0
- package/dist/route-pattern/join.js +2 -0
- package/dist/route-pattern/match.d.ts +2 -0
- package/dist/route-pattern/match.d.ts.map +1 -0
- package/dist/route-pattern/match.js +2 -0
- package/dist/ui/accordion/primitives.d.ts +2 -0
- package/dist/ui/accordion/primitives.d.ts.map +1 -0
- package/dist/ui/accordion/primitives.js +2 -0
- package/dist/ui/button.d.ts +1 -0
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +1 -0
- package/dist/ui/checkbox.d.ts +3 -0
- package/dist/ui/checkbox.d.ts.map +1 -0
- package/dist/ui/checkbox.js +3 -0
- package/dist/ui/combobox/primitives.d.ts +2 -0
- package/dist/ui/combobox/primitives.d.ts.map +1 -0
- package/dist/ui/combobox/primitives.js +2 -0
- package/dist/ui/dev/refresh.d.ts +2 -0
- package/dist/ui/dev/refresh.d.ts.map +1 -0
- package/dist/ui/dev/refresh.js +2 -0
- package/dist/ui/input.d.ts +3 -0
- package/dist/ui/input.d.ts.map +1 -0
- package/dist/ui/input.js +3 -0
- package/dist/ui/menu/primitives.d.ts +2 -0
- package/dist/ui/menu/primitives.d.ts.map +1 -0
- package/dist/ui/menu/primitives.js +2 -0
- package/dist/ui/radio.d.ts +3 -0
- package/dist/ui/radio.d.ts.map +1 -0
- package/dist/ui/radio.js +3 -0
- package/dist/ui/select/primitives.d.ts +2 -0
- package/dist/ui/select/primitives.d.ts.map +1 -0
- package/dist/ui/select/primitives.js +2 -0
- package/dist/ui/tabs/primitives.d.ts +2 -0
- package/dist/ui/tabs/primitives.d.ts.map +1 -0
- package/dist/ui/tabs/primitives.js +2 -0
- package/dist/ui/tabs.d.ts +2 -0
- package/dist/ui/tabs.d.ts.map +1 -0
- package/{src/ui/theme.ts → dist/ui/tabs.js} +1 -1
- package/dist/ui/toggle/primitives.d.ts +2 -0
- package/dist/ui/toggle/primitives.d.ts.map +1 -0
- package/dist/ui/toggle/primitives.js +2 -0
- package/dist/ui/toggle.d.ts +3 -0
- package/dist/ui/toggle.d.ts.map +1 -0
- package/dist/ui/toggle.js +3 -0
- package/dist/ui-hmr/assets.d.ts +2 -0
- package/dist/ui-hmr/assets.d.ts.map +1 -0
- package/dist/ui-hmr/assets.js +2 -0
- package/dist/ui-hmr/node.d.ts +3 -0
- package/dist/ui-hmr/node.d.ts.map +1 -0
- package/dist/ui-hmr/node.js +3 -0
- package/dist/ui-hmr/runtime/browser.d.ts +2 -0
- package/dist/ui-hmr/runtime/browser.d.ts.map +1 -0
- package/dist/ui-hmr/runtime/browser.js +2 -0
- package/dist/ui-hmr/runtime/server.d.ts +2 -0
- package/dist/ui-hmr/runtime/server.d.ts.map +1 -0
- package/dist/ui-hmr/runtime/server.js +2 -0
- package/dist/ui-hmr.d.ts +2 -0
- package/dist/ui-hmr.d.ts.map +1 -0
- package/{src/ui/glyph.ts → dist/ui-hmr.js} +1 -1
- package/package.json +284 -129
- package/src/assert/README.md +116 -0
- package/src/assets/README.md +805 -0
- package/src/assets/types/hmr.d.ts +2 -0
- package/src/async-context-middleware/README.md +100 -0
- package/src/auth/README.md +445 -0
- package/src/auth-middleware/README.md +246 -0
- package/src/cli/README.md +182 -0
- package/src/compression-middleware/README.md +176 -0
- package/src/cookie/README.md +106 -0
- package/src/cop-middleware/README.md +117 -0
- package/src/cors-middleware/README.md +174 -0
- package/src/csrf-middleware/README.md +92 -0
- package/src/data-schema/README.md +416 -0
- package/src/data-table/README.md +678 -0
- package/src/data-table/cli.ts +2 -0
- package/src/data-table-mysql/README.md +120 -0
- package/src/data-table-postgres/README.md +98 -0
- package/src/data-table-sqlite/README.md +100 -0
- package/src/fetch-proxy/README.md +70 -0
- package/src/fetch-router/README.md +982 -0
- package/src/fetch-router.ts +7 -0
- package/src/file-storage/README.md +57 -0
- package/src/file-storage-s3/README.md +46 -0
- package/src/form-data-middleware/README.md +108 -0
- package/src/form-data-parser/README.md +158 -0
- package/src/fs/README.md +60 -0
- package/src/headers/README.md +639 -0
- package/src/headers/accept-encoding.ts +2 -0
- package/src/headers/accept-language.ts +2 -0
- package/src/headers/accept.ts +2 -0
- package/src/headers/cache-control.ts +2 -0
- package/src/headers/content-disposition.ts +2 -0
- package/src/headers/content-range.ts +2 -0
- package/src/headers/content-type.ts +2 -0
- package/src/headers/cookie.ts +2 -0
- package/src/headers/if-match.ts +2 -0
- package/src/headers/if-none-match.ts +2 -0
- package/src/headers/if-range.ts +2 -0
- package/src/headers/range.ts +2 -0
- package/src/headers/raw-headers.ts +2 -0
- package/src/headers/set-cookie.ts +2 -0
- package/src/{node-serve.ts → headers/vary.ts} +1 -1
- package/src/html-template/README.md +101 -0
- package/src/lazy-file/README.md +109 -0
- package/src/logger-middleware/README.md +132 -0
- package/src/method-override-middleware/README.md +71 -0
- package/src/mime/README.md +117 -0
- package/src/multipart-parser/README.md +241 -0
- package/src/node-fetch-server/README.md +377 -0
- package/src/node-hmr/README.md +307 -0
- package/src/node-hmr/runtime.ts +2 -0
- package/src/node-hmr/types.d.ts +2 -0
- package/{dist/ui/theme.js → src/node-hmr.ts} +1 -1
- package/src/node-tsx/README.md +74 -0
- package/src/node-tsx/load-module.ts +2 -0
- package/{dist/node-serve.js → src/node-tsx.ts} +2 -1
- package/src/render-middleware/README.md +99 -0
- package/src/render-middleware.ts +2 -0
- package/src/route-pattern/README.md +424 -0
- package/src/route-pattern/href.ts +2 -0
- package/src/route-pattern/join.ts +2 -0
- package/src/route-pattern/match.ts +2 -0
- package/src/session/README.md +171 -0
- package/src/session-middleware/README.md +111 -0
- package/src/session-storage-memcache/README.md +37 -0
- package/src/session-storage-redis/README.md +36 -0
- package/src/static-middleware/README.md +89 -0
- package/src/tar-parser/README.md +74 -0
- package/src/terminal/README.md +92 -0
- package/src/test/README.md +500 -0
- package/src/ui/README.md +216 -0
- package/src/ui/accordion/README.md +202 -0
- package/src/ui/accordion/primitives/README.md +202 -0
- package/src/ui/accordion/primitives.ts +2 -0
- package/src/ui/anchor/README.md +196 -0
- package/src/ui/animation/README.md +316 -0
- package/src/ui/breadcrumbs/README.md +55 -0
- package/src/ui/button/README.md +44 -0
- package/src/ui/button.ts +1 -0
- package/src/ui/checkbox/README.md +59 -0
- package/src/ui/checkbox.ts +3 -0
- package/src/ui/combobox/README.md +194 -0
- package/src/ui/combobox/primitives/README.md +194 -0
- package/src/ui/combobox/primitives.ts +2 -0
- package/src/ui/dev/refresh.ts +2 -0
- package/src/ui/input/README.md +52 -0
- package/src/ui/input.ts +3 -0
- package/src/ui/listbox/README.md +83 -0
- package/src/ui/menu/README.md +161 -0
- package/src/ui/menu/primitives/README.md +161 -0
- package/src/ui/menu/primitives.ts +2 -0
- package/src/ui/popover/README.md +103 -0
- package/src/ui/radio/README.md +53 -0
- package/src/ui/radio.ts +3 -0
- package/src/ui/select/README.md +117 -0
- package/src/ui/select/primitives/README.md +117 -0
- package/src/ui/select/primitives.ts +2 -0
- package/src/ui/server/README.md +103 -0
- package/src/ui/tabs/README.md +141 -0
- package/src/ui/tabs/primitives/README.md +141 -0
- package/src/ui/tabs/primitives.ts +2 -0
- package/src/ui/tabs.ts +2 -0
- package/src/ui/test/README.md +198 -0
- package/src/ui/toggle/README.md +56 -0
- package/src/ui/toggle/primitives/README.md +56 -0
- package/src/ui/toggle/primitives.ts +2 -0
- package/src/ui/toggle.ts +3 -0
- package/src/ui-hmr/README.md +119 -0
- package/src/ui-hmr/assets.ts +2 -0
- package/src/ui-hmr/node.ts +3 -0
- package/src/ui-hmr/runtime/browser.ts +2 -0
- package/src/ui-hmr/runtime/server.ts +2 -0
- package/src/ui-hmr.ts +2 -0
- package/dist/node-serve.d.ts +0 -2
- package/dist/node-serve.d.ts.map +0 -1
- package/dist/ui/glyph.d.ts +0 -2
- package/dist/ui/glyph.d.ts.map +0 -1
- package/dist/ui/scroll-lock.d.ts +0 -2
- package/dist/ui/scroll-lock.d.ts.map +0 -1
- package/dist/ui/separator.d.ts +0 -2
- package/dist/ui/separator.d.ts.map +0 -1
- package/dist/ui/theme.d.ts +0 -2
- package/dist/ui/theme.d.ts.map +0 -1
- package/src/ui/scroll-lock.ts +0 -2
- package/src/ui/separator.ts +0 -2
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
# assets
|
|
2
|
+
|
|
3
|
+
Fetch-based server for compiling browser assets on demand.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **On-Demand Compilation** - Compile browser scripts and styles on demand
|
|
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 and packages can be served
|
|
11
|
+
- **Preloads** - Generate preload URLs for scripts and styles based on imports
|
|
12
|
+
- **Caching** - Conservative caching by default with stable URLs, ETags, and revalidation
|
|
13
|
+
- **Optional Fingerprinting** - Source-based fingerprinted URLs for long-lived browser caching
|
|
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
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm i remix
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
Use `createAssetServer` to serve browser assets from a URL namespace in your app.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { createRouter } from 'remix/router'
|
|
30
|
+
import { createAssetServer } from 'remix/assets'
|
|
31
|
+
|
|
32
|
+
let assetServer = createAssetServer({
|
|
33
|
+
basePath: '/assets',
|
|
34
|
+
fileMap: {
|
|
35
|
+
'/app/*path': 'app/*path',
|
|
36
|
+
'/npm/*path': 'node_modules/*path',
|
|
37
|
+
},
|
|
38
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
39
|
+
allowPackages: ['remix'],
|
|
40
|
+
files: {
|
|
41
|
+
extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
let router = createRouter()
|
|
46
|
+
|
|
47
|
+
router.get('/assets/*', ({ request }) => {
|
|
48
|
+
return assetServer.fetch(request)
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This example gives you an `/assets/*` endpoint that serves compiled browser source from `public/` directories throughout `app/` and from the `remix` package.
|
|
53
|
+
|
|
54
|
+
## Root Directory
|
|
55
|
+
|
|
56
|
+
Use `rootDir` to specify the root directory of the asset server, which is used to resolve relative file paths. Defaults to `process.cwd()`.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import * as path from 'node:path'
|
|
60
|
+
import { createAssetServer } from 'remix/assets'
|
|
61
|
+
|
|
62
|
+
let assetServer = createAssetServer({
|
|
63
|
+
rootDir: path.resolve(import.meta.dirname, '..'),
|
|
64
|
+
basePath: '/assets',
|
|
65
|
+
fileMap: {
|
|
66
|
+
'/app/*path': 'app/*path',
|
|
67
|
+
'/npm/*path': 'node_modules/*path',
|
|
68
|
+
},
|
|
69
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
70
|
+
allowPackages: ['remix'],
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Access Control
|
|
75
|
+
|
|
76
|
+
You must provide an `allowFiles` list to specify which files are allowed to be served. You can also allow whole packages by name with `allowPackages`. `denyFiles` is optional and takes precedence over both `allowFiles` and `allowPackages`.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { createAssetServer } from 'remix/assets'
|
|
80
|
+
|
|
81
|
+
let assetServer = createAssetServer({
|
|
82
|
+
basePath: '/assets',
|
|
83
|
+
fileMap: {
|
|
84
|
+
'/app/*path': 'app/*path',
|
|
85
|
+
'/npm/*path': 'node_modules/*path',
|
|
86
|
+
},
|
|
87
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
88
|
+
allowPackages: ['remix'],
|
|
89
|
+
denyFiles: ['app/**/*.server.*'],
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Values for `allowFiles` and `denyFiles` are file paths or globs. Relative values are resolved from `rootDir`. Absolute file paths match exactly, and absolute directory paths also match their descendants.
|
|
94
|
+
|
|
95
|
+
Values for `allowPackages` are exact package names. Dependencies and installed optional dependencies of packages in `allowPackages` are also allowed automatically. Peer dependencies must be listed explicitly if they should be browser-reachable. Allowed package files must still be reachable through `fileMap`.
|
|
96
|
+
|
|
97
|
+
## File Map
|
|
98
|
+
|
|
99
|
+
Use `fileMap` to map public URLs to file paths on disk. `basePath` defines the shared public mount point, and the `fileMap` keys are URL patterns relative to that base path. The values are root-relative file path patterns.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { createAssetServer } from 'remix/assets'
|
|
103
|
+
|
|
104
|
+
let assetServer = createAssetServer({
|
|
105
|
+
basePath: '/assets',
|
|
106
|
+
fileMap: {
|
|
107
|
+
'/app/*path': 'app/*path',
|
|
108
|
+
'/npm/*path': 'node_modules/*path',
|
|
109
|
+
},
|
|
110
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
111
|
+
allowPackages: ['remix'],
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
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
|
+
### File watching
|
|
118
|
+
|
|
119
|
+
The file system is watched by default so source changes are picked up without requiring a server restart.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { createAssetServer } from 'remix/assets'
|
|
123
|
+
|
|
124
|
+
let assetServer = createAssetServer({
|
|
125
|
+
basePath: '/assets',
|
|
126
|
+
fileMap: {
|
|
127
|
+
'/app/*path': 'app/*path',
|
|
128
|
+
'/npm/*path': 'node_modules/*path',
|
|
129
|
+
},
|
|
130
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
131
|
+
allowPackages: ['remix'],
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
When finished with the asset server, call `await assetServer.close()` to clean up the file watcher.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
await assetServer.close()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
You can disable file watching if the files on disk won't change, or if watching is managed at a higher level (e.g. Node's `--watch` flag).
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { createAssetServer } from 'remix/assets'
|
|
145
|
+
|
|
146
|
+
let assetServer = createAssetServer({
|
|
147
|
+
basePath: '/assets',
|
|
148
|
+
fileMap: {
|
|
149
|
+
'/app/*path': 'app/*path',
|
|
150
|
+
'/npm/*path': 'node_modules/*path',
|
|
151
|
+
},
|
|
152
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
153
|
+
allowPackages: ['remix'],
|
|
154
|
+
watch: false,
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
You can optionally provide an array of glob patterns to the `watch.ignore` option:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { createAssetServer } from 'remix/assets'
|
|
162
|
+
|
|
163
|
+
let assetServer = createAssetServer({
|
|
164
|
+
basePath: '/assets',
|
|
165
|
+
fileMap: {
|
|
166
|
+
'/app/*path': 'app/*path',
|
|
167
|
+
'/npm/*path': 'node_modules/*path',
|
|
168
|
+
},
|
|
169
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
170
|
+
allowPackages: ['remix'],
|
|
171
|
+
watch: {
|
|
172
|
+
ignore: ['**/node_modules/**'],
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Hrefs
|
|
178
|
+
|
|
179
|
+
Use `assetServer.getHref()` when you need the public URL for a served asset. You can provide a root-relative or absolute file path, or a `file://` URL.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
let src = await assetServer.getHref('app/actions/public/entry.ts')
|
|
183
|
+
// '/assets/app/actions/public/entry.ts'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
For configured `files` assets, you can also pass a `transform` pipeline to build a request URL with custom file transforms. Basic transforms are written as strings, while dynamic transforms use `[name, param]` tuples.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
let src = await assetServer.getHref('app/media/public/image.png', {
|
|
190
|
+
transform: [['resize', '100x100'], 'webp'],
|
|
191
|
+
})
|
|
192
|
+
// '/assets/app/media/public/image.png?transform=resize%3A100x100&transform=webp'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Preloads
|
|
196
|
+
|
|
197
|
+
Use `assetServer.getPreloads()` when rendering HTML so you can turn the returned URLs into `<link rel="modulepreload">`, stylesheet preload tags, or `Link` headers for one or more assets and their dependencies. You can provide root-relative or absolute file paths, or `file://` URLs.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
let preloads = await assetServer.getPreloads([
|
|
201
|
+
'app/actions/public/entry.ts',
|
|
202
|
+
'app/search/public/search.tsx',
|
|
203
|
+
])
|
|
204
|
+
// [
|
|
205
|
+
// '/assets/app/actions/public/entry.ts',
|
|
206
|
+
// '/assets/app/search/public/search.tsx',
|
|
207
|
+
// '/assets/app/search/public/utils.ts',
|
|
208
|
+
// '/assets/npm/remix/ui/index.js',
|
|
209
|
+
// ...etc
|
|
210
|
+
// ]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Fingerprinting
|
|
214
|
+
|
|
215
|
+
By default, assets are served at stable URLs with ETags and `Cache-Control: no-cache`.
|
|
216
|
+
|
|
217
|
+
If you want clients to cache assets aggressively without revalidation, you can opt into source-based fingerprinting.
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { createAssetServer } from 'remix/assets'
|
|
221
|
+
|
|
222
|
+
let assetServer = createAssetServer({
|
|
223
|
+
basePath: '/assets',
|
|
224
|
+
fileMap: {
|
|
225
|
+
'/app/*path': 'app/*path',
|
|
226
|
+
'/npm/*path': 'node_modules/*path',
|
|
227
|
+
},
|
|
228
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
229
|
+
allowPackages: ['remix'],
|
|
230
|
+
watch: false,
|
|
231
|
+
fingerprint: {
|
|
232
|
+
buildId: process.env.GITHUB_SHA,
|
|
233
|
+
},
|
|
234
|
+
})
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
When fingerprinting is enabled, assets use a `.@<fingerprint>` segment before the file extension and are served with `Cache-Control: public, max-age=31536000, immutable`.
|
|
238
|
+
|
|
239
|
+
Source fingerprints are based on the original file contents and the build ID. The build ID must change for each deployment so that fingerprinted assets are invalidated together. This fingerprinting strategy assumes that files on disk won't change, so fingerprinting requires `watch: false`.
|
|
240
|
+
|
|
241
|
+
## Target
|
|
242
|
+
|
|
243
|
+
Use `target` to lower emitted syntax to a specific browser support policy and/or ECMAScript version.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { createAssetServer } from 'remix/assets'
|
|
247
|
+
|
|
248
|
+
let assetServer = createAssetServer({
|
|
249
|
+
basePath: '/assets',
|
|
250
|
+
fileMap: {
|
|
251
|
+
'/app/*path': 'app/*path',
|
|
252
|
+
'/npm/*path': 'node_modules/*path',
|
|
253
|
+
},
|
|
254
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
255
|
+
allowPackages: ['remix'],
|
|
256
|
+
target: {
|
|
257
|
+
chrome: '109',
|
|
258
|
+
ios: '15.6',
|
|
259
|
+
es: '2020',
|
|
260
|
+
},
|
|
261
|
+
})
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Supported target options are `chrome`, `firefox`, `safari`, `edge`, `opera`, `ios`, `samsung`, and `es` (ECMAScript version).
|
|
265
|
+
|
|
266
|
+
### Source Maps
|
|
267
|
+
|
|
268
|
+
Enable sourcemaps with either `'external'` or `'inline'` using `sourceMaps`:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { createAssetServer } from 'remix/assets'
|
|
272
|
+
|
|
273
|
+
let assetServer = createAssetServer({
|
|
274
|
+
basePath: '/assets',
|
|
275
|
+
fileMap: {
|
|
276
|
+
'/app/*path': 'app/*path',
|
|
277
|
+
'/npm/*path': 'node_modules/*path',
|
|
278
|
+
},
|
|
279
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
280
|
+
allowPackages: ['remix'],
|
|
281
|
+
sourceMaps: 'external',
|
|
282
|
+
})
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
By default, sourcemap `sources` use URLs so they're presented alongside the compiled output in your browser's developer tools. You can also use file system paths instead with `sourceMapSourcePaths`:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { createAssetServer } from 'remix/assets'
|
|
289
|
+
|
|
290
|
+
let assetServer = createAssetServer({
|
|
291
|
+
basePath: '/assets',
|
|
292
|
+
fileMap: {
|
|
293
|
+
'/app/*path': 'app/*path',
|
|
294
|
+
'/npm/*path': 'node_modules/*path',
|
|
295
|
+
},
|
|
296
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
297
|
+
allowPackages: ['remix'],
|
|
298
|
+
sourceMaps: 'inline',
|
|
299
|
+
sourceMapSourcePaths: 'absolute',
|
|
300
|
+
})
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Minification
|
|
304
|
+
|
|
305
|
+
Enable minification with `minify`:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
import { createAssetServer } from 'remix/assets'
|
|
309
|
+
|
|
310
|
+
let assetServer = createAssetServer({
|
|
311
|
+
basePath: '/assets',
|
|
312
|
+
fileMap: {
|
|
313
|
+
'/app/*path': 'app/*path',
|
|
314
|
+
'/npm/*path': 'node_modules/*path',
|
|
315
|
+
},
|
|
316
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
317
|
+
allowPackages: ['remix'],
|
|
318
|
+
minify: true,
|
|
319
|
+
})
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Script Options
|
|
323
|
+
|
|
324
|
+
### Define
|
|
325
|
+
|
|
326
|
+
Use `scripts.define` to replace global identifiers with constant expressions.
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
import { createAssetServer } from 'remix/assets'
|
|
330
|
+
|
|
331
|
+
let assetServer = createAssetServer({
|
|
332
|
+
basePath: '/assets',
|
|
333
|
+
fileMap: {
|
|
334
|
+
'/app/*path': 'app/*path',
|
|
335
|
+
'/npm/*path': 'node_modules/*path',
|
|
336
|
+
},
|
|
337
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
338
|
+
allowPackages: ['remix'],
|
|
339
|
+
scripts: {
|
|
340
|
+
define: {
|
|
341
|
+
'process.env.NODE_ENV': '"production"',
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
})
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Values are injected exactly as defined, so string literals must include their own quotes, e.g. `process.env.NODE_ENV` must be `"production"` rather than `production`.
|
|
348
|
+
|
|
349
|
+
### External Imports
|
|
350
|
+
|
|
351
|
+
Use `scripts.external` to leave specific import specifiers unchanged by providing an array of specifiers.
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
import { createAssetServer } from 'remix/assets'
|
|
355
|
+
|
|
356
|
+
let assetServer = createAssetServer({
|
|
357
|
+
basePath: '/assets',
|
|
358
|
+
fileMap: {
|
|
359
|
+
'/app/*path': 'app/*path',
|
|
360
|
+
'/npm/*path': 'node_modules/*path',
|
|
361
|
+
},
|
|
362
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
363
|
+
allowPackages: ['remix'],
|
|
364
|
+
scripts: {
|
|
365
|
+
external: ['my-external-import'],
|
|
366
|
+
},
|
|
367
|
+
})
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Loaders
|
|
371
|
+
|
|
372
|
+
Use `scripts.loaders` to post-process compiled JavaScript. Loaders use the same function signature and chaining behavior as synchronous [`load` hooks in Node's module API](https://nodejs.org/api/module.html#customization-hooks).
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
import { createAssetServer } from 'remix/assets'
|
|
376
|
+
|
|
377
|
+
let assetServer = createAssetServer({
|
|
378
|
+
basePath: '/assets',
|
|
379
|
+
fileMap: { '/app/*path': 'app/*path' },
|
|
380
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
381
|
+
denyFiles: ['app/**/*.test.*'],
|
|
382
|
+
scripts: {
|
|
383
|
+
loaders: [
|
|
384
|
+
(url, context, nextLoad) => {
|
|
385
|
+
let result = nextLoad(url, context)
|
|
386
|
+
return {
|
|
387
|
+
...result,
|
|
388
|
+
source: `${result.source}\nconsole.log('loaded')`,
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
})
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Loaders receive JavaScript after the asset server transforms TypeScript and JavaScript, and run before HMR analysis and minification. They must return `format: 'module'`. Import attributes are not supported.
|
|
397
|
+
|
|
398
|
+
## File Options
|
|
399
|
+
|
|
400
|
+
Use `files` to serve additional leaf assets like images and fonts. File extensions must include the leading dot and are only served when explicitly configured.
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
import { createAssetServer } from 'remix/assets'
|
|
404
|
+
|
|
405
|
+
let assetServer = createAssetServer({
|
|
406
|
+
basePath: '/assets',
|
|
407
|
+
fileMap: {
|
|
408
|
+
'/app/*path': 'app/*path',
|
|
409
|
+
'/npm/*path': 'node_modules/*path',
|
|
410
|
+
},
|
|
411
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
412
|
+
allowPackages: ['remix'],
|
|
413
|
+
files: {
|
|
414
|
+
extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
|
|
415
|
+
},
|
|
416
|
+
})
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
JavaScript/TypeScript and CSS extensions not supported in `files.extensions` as they are not leaf assets and have their own module systems.
|
|
420
|
+
|
|
421
|
+
### File transforms
|
|
422
|
+
|
|
423
|
+
Files can optionally be transformed before serving.
|
|
424
|
+
|
|
425
|
+
Use `files.transforms` for named transforms that callers can opt into per request, provided via the `transform` option when calling `assetServer.getHref()`.
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
import { createAssetServer, defineFileTransform } from 'remix/assets'
|
|
429
|
+
import sharp from 'sharp'
|
|
430
|
+
|
|
431
|
+
let assetServer = createAssetServer({
|
|
432
|
+
basePath: '/assets',
|
|
433
|
+
fileMap: {
|
|
434
|
+
'/app/*path': 'app/*path',
|
|
435
|
+
'/npm/*path': 'node_modules/*path',
|
|
436
|
+
},
|
|
437
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
438
|
+
allowPackages: ['remix'],
|
|
439
|
+
files: {
|
|
440
|
+
extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
|
|
441
|
+
transforms: {
|
|
442
|
+
webp: defineFileTransform({
|
|
443
|
+
extensions: ['.png', '.jpg', '.jpeg'],
|
|
444
|
+
async transform(bytes) {
|
|
445
|
+
return {
|
|
446
|
+
content: await sharp(bytes).webp({ quality: 80 }).toBuffer(),
|
|
447
|
+
extension: '.webp',
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
}),
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
let imageUrl = await assetServer.getHref('app/media/public/photo.jpg', {
|
|
456
|
+
transform: ['webp'],
|
|
457
|
+
})
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Transforms can also accept a single string param value, provided as a `[name, param]` tuple in the `transform` array when calling `assetServer.getHref()`.
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
import { createAssetServer, defineFileTransform } from 'remix/assets'
|
|
464
|
+
|
|
465
|
+
let assetServer = createAssetServer({
|
|
466
|
+
basePath: '/assets',
|
|
467
|
+
fileMap: {
|
|
468
|
+
'/app/*path': 'app/*path',
|
|
469
|
+
'/npm/*path': 'node_modules/*path',
|
|
470
|
+
},
|
|
471
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
472
|
+
allowPackages: ['remix'],
|
|
473
|
+
files: {
|
|
474
|
+
extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
|
|
475
|
+
transforms: {
|
|
476
|
+
recolor: defineFileTransform({
|
|
477
|
+
extensions: ['.svg'],
|
|
478
|
+
param: true,
|
|
479
|
+
async transform(bytes, { param }) {
|
|
480
|
+
if (!/^#?(?:[\da-f]{3,4}|[\da-f]{6}(?:[\da-f]{2})?)$/i.test(param)) {
|
|
481
|
+
throw new TypeError('Expected a hex color, with or without a leading #')
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
let svg = new TextDecoder().decode(bytes)
|
|
485
|
+
return svg.replaceAll('currentColor', `${!param.startsWith('#') ? '#' : ''}${param}`)
|
|
486
|
+
},
|
|
487
|
+
}),
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
let imageUrl = await assetServer.getHref('app/media/public/logo.svg', {
|
|
493
|
+
transform: [['recolor', '0000ff']],
|
|
494
|
+
})
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Hand-authored URLs use repeated `transform` search params with `name` or `name:param` values:
|
|
498
|
+
|
|
499
|
+
```css
|
|
500
|
+
.selector {
|
|
501
|
+
background-image: url('/assets/app/media/public/image.png?transform=resize:100x100&transform=webp');
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
#### Global file transforms
|
|
506
|
+
|
|
507
|
+
Use `files.globalTransforms` to define transforms that should always happen before a file is served. These transforms are run after any request-level transforms for all configured file extensions, and can return `null` to skip themselves for a given input.
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
import { createAssetServer } from 'remix/assets'
|
|
511
|
+
import { optimize as optimizeSvg } from 'svgo'
|
|
512
|
+
|
|
513
|
+
let assetServer = createAssetServer({
|
|
514
|
+
basePath: '/assets',
|
|
515
|
+
fileMap: {
|
|
516
|
+
'/app/*path': 'app/*path',
|
|
517
|
+
'/npm/*path': 'node_modules/*path',
|
|
518
|
+
},
|
|
519
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
520
|
+
allowPackages: ['remix'],
|
|
521
|
+
files: {
|
|
522
|
+
extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
|
|
523
|
+
globalTransforms: [
|
|
524
|
+
{
|
|
525
|
+
extensions: ['.svg'],
|
|
526
|
+
async transform(bytes) {
|
|
527
|
+
let svg = new TextDecoder().decode(bytes)
|
|
528
|
+
return optimizeSvg(svg, { multipass: true }).data
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
},
|
|
533
|
+
})
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
#### File transform caching
|
|
537
|
+
|
|
538
|
+
Use `files.cache` to store transformed file outputs via a [`file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage) backend.
|
|
539
|
+
|
|
540
|
+
Without `files.cache`, transformed file outputs are recomputed per request.
|
|
541
|
+
|
|
542
|
+
If `fingerprint.buildId` is set, the file cache can be reused across server restarts for the same build.
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
import * as path from 'node:path'
|
|
546
|
+
import { createAssetServer } from 'remix/assets'
|
|
547
|
+
import { createFsFileStorage } from 'remix/file-storage/fs'
|
|
548
|
+
|
|
549
|
+
let assetServer = createAssetServer({
|
|
550
|
+
basePath: '/assets',
|
|
551
|
+
fileMap: {
|
|
552
|
+
'/app/*path': 'app/*path',
|
|
553
|
+
'/npm/*path': 'node_modules/*path',
|
|
554
|
+
},
|
|
555
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
556
|
+
allowPackages: ['remix'],
|
|
557
|
+
files: {
|
|
558
|
+
cache: createFsFileStorage(path.resolve('.tmp/assets-cache')),
|
|
559
|
+
extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
|
|
560
|
+
transforms: {
|
|
561
|
+
/*...*/
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
})
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
#### Request transform limits
|
|
568
|
+
|
|
569
|
+
Use `files.maxRequestTransforms` to cap request transform pipelines. It defaults to `5`.
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
import { createAssetServer } from 'remix/assets'
|
|
573
|
+
|
|
574
|
+
let assetServer = createAssetServer({
|
|
575
|
+
basePath: '/assets',
|
|
576
|
+
fileMap: {
|
|
577
|
+
'/app/*path': 'app/*path',
|
|
578
|
+
'/npm/*path': 'node_modules/*path',
|
|
579
|
+
},
|
|
580
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
581
|
+
allowPackages: ['remix'],
|
|
582
|
+
files: {
|
|
583
|
+
maxRequestTransforms: 5,
|
|
584
|
+
extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
|
|
585
|
+
transforms: {
|
|
586
|
+
/*...*/
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
})
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
## CSS Imports
|
|
593
|
+
|
|
594
|
+
Relative CSS `@import` rules and `url()` references are rewritten to asset server URLs.
|
|
595
|
+
|
|
596
|
+
```css
|
|
597
|
+
/* Rewritten to asset server URLs: */
|
|
598
|
+
@import './reset.css';
|
|
599
|
+
.selector {
|
|
600
|
+
background-image: url('./image.png');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/* External URLs: */
|
|
604
|
+
@import 'https://fonts.googleapis.com/css2?family=Inter';
|
|
605
|
+
.selector {
|
|
606
|
+
background-image: url('https://example.com/logo.svg');
|
|
607
|
+
}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
File transforms can also be applied to relative CSS `url()` references:
|
|
611
|
+
|
|
612
|
+
```css
|
|
613
|
+
.selector {
|
|
614
|
+
background-image: url('./image.png?transform=resize:100x100&transform=webp');
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
## Error Handling
|
|
619
|
+
|
|
620
|
+
Use `onError` to report unexpected compilation failures and/or return a custom response.
|
|
621
|
+
|
|
622
|
+
```ts
|
|
623
|
+
import { createAssetServer } from 'remix/assets'
|
|
624
|
+
|
|
625
|
+
let assetServer = createAssetServer({
|
|
626
|
+
basePath: '/assets',
|
|
627
|
+
fileMap: {
|
|
628
|
+
'/app/*path': 'app/*path',
|
|
629
|
+
'/npm/*path': 'node_modules/*path',
|
|
630
|
+
},
|
|
631
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
632
|
+
allowPackages: ['remix'],
|
|
633
|
+
onError(error) {
|
|
634
|
+
console.error('Failed to build client assets', error)
|
|
635
|
+
return new Response('Client asset build failed', { status: 500 })
|
|
636
|
+
},
|
|
637
|
+
})
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
If `onError` returns nothing, the asset server responds with the default `500 Internal Server Error` response.
|
|
641
|
+
|
|
642
|
+
## Hot Module Reloading
|
|
643
|
+
|
|
644
|
+
Use `hmr` with `watch` to enable the `import.meta.hot` API for browser modules. The `hmr` option is designed for integrating assets with a server-level HMR runtime such as [`node-hmr`](https://github.com/remix-run/remix/tree/main/packages/node-hmr) so server and browser updates can be coordinated.
|
|
645
|
+
|
|
646
|
+
The `hmr` option accepts an async function that creates a `BrowserHmrChannel`, such as the `createBrowserHmrChannel` function from [`node-hmr`](https://github.com/remix-run/remix/tree/main/packages/node-hmr):
|
|
647
|
+
|
|
648
|
+
```ts
|
|
649
|
+
import { createAssetServer } from 'remix/assets'
|
|
650
|
+
|
|
651
|
+
let isDevelopment = process.env.NODE_ENV === 'development'
|
|
652
|
+
let assetServer = createAssetServer({
|
|
653
|
+
basePath: '/assets',
|
|
654
|
+
fileMap: { '/app/*path': 'app/*path' },
|
|
655
|
+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
|
|
656
|
+
denyFiles: ['app/**/*.test.*'],
|
|
657
|
+
hmr: isDevelopment
|
|
658
|
+
? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
|
|
659
|
+
: undefined,
|
|
660
|
+
watch: isDevelopment,
|
|
661
|
+
})
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### `import.meta.hot`
|
|
665
|
+
|
|
666
|
+
The `import.meta.hot` API provided by `assets` is a small runtime contract for modules that can handle updates without reloading the page. It is primarily intended for browser modules compiled by `assets`, but it can also be used directly.
|
|
667
|
+
|
|
668
|
+
To type `import.meta.hot`, add the HMR types to your TypeScript config:
|
|
669
|
+
|
|
670
|
+
```json
|
|
671
|
+
{
|
|
672
|
+
"compilerOptions": {
|
|
673
|
+
"types": ["remix/assets/types/hmr"]
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
HMR accept calls are statically analyzed. Write them directly as `import.meta.hot.accept(...)`. Dependency accepts must use string literals or arrays of string literals; do not alias `import.meta.hot` or pass dynamically constructed dependency lists.
|
|
679
|
+
|
|
680
|
+
```ts
|
|
681
|
+
if (import.meta.hot) {
|
|
682
|
+
import.meta.hot.accept()
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### Accepting updates
|
|
687
|
+
|
|
688
|
+
Calling `accept()` makes the current module an HMR boundary. When the module changes, `assets` evaluates the updated module and calls your callback with its exports.
|
|
689
|
+
|
|
690
|
+
```ts
|
|
691
|
+
export let value = 1
|
|
692
|
+
|
|
693
|
+
if (import.meta.hot) {
|
|
694
|
+
import.meta.hot.accept((module) => {
|
|
695
|
+
if (typeof module.value !== 'number') {
|
|
696
|
+
import.meta.hot?.invalidate('Updated module no longer exports value')
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
value = module.value
|
|
701
|
+
})
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
You can also accept updates from direct dependencies.
|
|
706
|
+
|
|
707
|
+
```ts
|
|
708
|
+
import { value } from './value.ts'
|
|
709
|
+
|
|
710
|
+
let currentValue = value
|
|
711
|
+
|
|
712
|
+
export function readValue() {
|
|
713
|
+
return currentValue
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if (import.meta.hot) {
|
|
717
|
+
import.meta.hot.accept('./value.ts', (module) => {
|
|
718
|
+
if (typeof module.value !== 'number') {
|
|
719
|
+
import.meta.hot?.invalidate('Updated dependency no longer exports value')
|
|
720
|
+
return
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
currentValue = module.value
|
|
724
|
+
})
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
Multiple dependencies can be accepted at once. The callback receives an array where only the changed dependency is defined.
|
|
729
|
+
|
|
730
|
+
```ts
|
|
731
|
+
if (import.meta.hot) {
|
|
732
|
+
import.meta.hot.accept(['./one.ts', './two.ts'], ([oneModule, twoModule]) => {
|
|
733
|
+
// oneModule is defined when ./one.ts changed.
|
|
734
|
+
// twoModule is defined when ./two.ts changed.
|
|
735
|
+
})
|
|
736
|
+
}
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Cleaning up
|
|
740
|
+
|
|
741
|
+
Register cleanup that should run before the module is replaced or disposed.
|
|
742
|
+
|
|
743
|
+
```ts
|
|
744
|
+
let interval = setInterval(refreshCache, 30_000)
|
|
745
|
+
|
|
746
|
+
if (import.meta.hot) {
|
|
747
|
+
import.meta.hot.dispose(() => {
|
|
748
|
+
clearInterval(interval)
|
|
749
|
+
})
|
|
750
|
+
}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
The `data` object is preserved across updates for the same module. Use it for small pieces of state.
|
|
754
|
+
|
|
755
|
+
```ts
|
|
756
|
+
let count = Number(import.meta.hot?.data.count ?? 0)
|
|
757
|
+
|
|
758
|
+
export function increment() {
|
|
759
|
+
count++
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (import.meta.hot) {
|
|
763
|
+
import.meta.hot.dispose((data) => {
|
|
764
|
+
data.count = count
|
|
765
|
+
})
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
### Invalidating updates
|
|
770
|
+
|
|
771
|
+
Call `invalidate()` inside an accept callback when the update cannot be applied safely. If no other boundary accepts the update, the browser reloads.
|
|
772
|
+
|
|
773
|
+
```ts
|
|
774
|
+
if (import.meta.hot) {
|
|
775
|
+
import.meta.hot.accept((module) => {
|
|
776
|
+
if (typeof module.value !== 'number') {
|
|
777
|
+
import.meta.hot?.invalidate('Updated module no longer exports value')
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
})
|
|
781
|
+
}
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
### Server update events
|
|
785
|
+
|
|
786
|
+
When the browser HMR channel comes from `remix/node-hmr/runtime`, server updates are sent to browser modules as `server:update` events.
|
|
787
|
+
|
|
788
|
+
```ts
|
|
789
|
+
if (import.meta.hot) {
|
|
790
|
+
import.meta.hot.on('server:update', () => {
|
|
791
|
+
window.location.reload()
|
|
792
|
+
})
|
|
793
|
+
}
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
## Related Packages
|
|
797
|
+
|
|
798
|
+
- [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - A Fetch-based router that pairs naturally with `assets`
|
|
799
|
+
- [`node-hmr`](https://github.com/remix-run/remix/tree/main/packages/node-hmr) - Provides the server-side `import.meta.hot` runtime and browser HMR channel used by `hmr`
|
|
800
|
+
- [`ui-hmr`](https://github.com/remix-run/remix/tree/main/packages/ui-hmr) - Provides a Remix UI component HMR loader for `scripts.loaders`
|
|
801
|
+
- [`route-pattern`](https://github.com/remix-run/remix/tree/main/packages/route-pattern) - Route-pattern syntax for URL and route file matching
|
|
802
|
+
|
|
803
|
+
## License
|
|
804
|
+
|
|
805
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|