remix 3.0.0-rc.1 → 3.0.0-rc.2

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.
@@ -0,0 +1,2 @@
1
+ export * from '@remix-run/multiple-import-maps-polyfill';
2
+ //# sourceMappingURL=multiple-import-maps-polyfill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multiple-import-maps-polyfill.d.ts","sourceRoot":"","sources":["../src/multiple-import-maps-polyfill.ts"],"names":[],"mappings":"AACA,cAAc,0CAA0C,CAAA"}
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/multiple-import-maps-polyfill';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix",
3
- "version": "3.0.0-rc.1",
3
+ "version": "3.0.0-rc.2",
4
4
  "description": "The Remix web framework",
5
5
  "author": "Michael Jackson <mjijackson@gmail.com>",
6
6
  "license": "MIT",
@@ -261,6 +261,10 @@
261
261
  "types": "./dist/multipart-parser/node.d.ts",
262
262
  "default": "./dist/multipart-parser/node.js"
263
263
  },
264
+ "./multiple-import-maps-polyfill": {
265
+ "types": "./dist/multiple-import-maps-polyfill.d.ts",
266
+ "default": "./dist/multiple-import-maps-polyfill.js"
267
+ },
264
268
  "./node-fetch-server": {
265
269
  "types": "./dist/node-fetch-server.d.ts",
266
270
  "default": "./dist/node-fetch-server.js"
@@ -512,53 +516,54 @@
512
516
  "typescript": "^7.0.2"
513
517
  },
514
518
  "dependencies": {
515
- "@remix-run/assets": "^0.6.0",
516
- "@remix-run/auth": "^0.3.0",
517
- "@remix-run/async-context-middleware": "^0.3.5",
518
- "@remix-run/auth-middleware": "^0.2.5",
519
- "@remix-run/ui": "^0.8.0",
520
- "@remix-run/compression-middleware": "^0.1.13",
521
- "@remix-run/cop-middleware": "^0.1.8",
522
- "@remix-run/cors-middleware": "^0.1.8",
523
- "@remix-run/csrf-middleware": "^0.1.8",
519
+ "@remix-run/auth": "^0.3.1",
520
+ "@remix-run/auth-middleware": "^0.2.6",
521
+ "@remix-run/ui": "^0.9.0",
522
+ "@remix-run/compression-middleware": "^0.1.14",
523
+ "@remix-run/assets": "^0.7.0",
524
+ "@remix-run/async-context-middleware": "^0.3.6",
525
+ "@remix-run/cors-middleware": "^0.1.9",
526
+ "@remix-run/csrf-middleware": "^0.1.9",
527
+ "@remix-run/data-table": "^0.5.1",
524
528
  "@remix-run/cookie": "^0.6.0",
525
- "@remix-run/data-table-postgres": "^0.5.1",
526
- "@remix-run/data-schema": "^0.3.0",
527
- "@remix-run/data-table": "^0.5.0",
528
- "@remix-run/data-table-mysql": "^0.5.1",
529
+ "@remix-run/data-table-postgres": "^0.5.2",
530
+ "@remix-run/cop-middleware": "^0.1.9",
531
+ "@remix-run/data-table-sqlite": "^0.6.2",
529
532
  "@remix-run/fetch-proxy": "^0.8.5",
533
+ "@remix-run/data-schema": "^0.3.0",
534
+ "@remix-run/fetch-router": "^0.22.0",
530
535
  "@remix-run/file-storage": "^0.13.7",
536
+ "@remix-run/data-table-mysql": "^0.5.2",
531
537
  "@remix-run/file-storage-s3": "^0.1.4",
532
- "@remix-run/form-data-middleware": "^0.3.5",
533
- "@remix-run/form-data-parser": "^0.17.5",
534
- "@remix-run/fetch-router": "^0.21.0",
535
- "@remix-run/data-table-sqlite": "^0.6.1",
538
+ "@remix-run/form-data-middleware": "^0.3.6",
536
539
  "@remix-run/fs": "^0.4.6",
537
- "@remix-run/headers": "^0.21.1",
538
540
  "@remix-run/lazy-file": "^5.0.6",
539
- "@remix-run/logger-middleware": "^0.3.5",
541
+ "@remix-run/logger-middleware": "^0.3.6",
542
+ "@remix-run/form-data-parser": "^0.17.5",
543
+ "@remix-run/method-override-middleware": "^0.1.14",
544
+ "@remix-run/headers": "^0.21.1",
540
545
  "@remix-run/html-template": "^0.3.1",
541
- "@remix-run/method-override-middleware": "^0.1.13",
542
546
  "@remix-run/mime": "^0.4.2",
543
- "@remix-run/multipart-parser": "^0.16.4",
544
- "@remix-run/node-fetch-server": "^0.14.1",
545
547
  "@remix-run/node-tsx": "^0.1.1",
548
+ "@remix-run/multipart-parser": "^0.16.4",
546
549
  "@remix-run/response": "^0.3.8",
547
- "@remix-run/session-middleware": "^0.4.0",
550
+ "@remix-run/node-fetch-server": "^0.14.1",
551
+ "@remix-run/route-pattern": "^0.24.0",
548
552
  "@remix-run/session": "^0.4.2",
549
- "@remix-run/tar-parser": "^0.7.1",
553
+ "@remix-run/session-middleware": "^0.4.1",
554
+ "@remix-run/session-storage-memcache": "^0.1.2",
550
555
  "@remix-run/assert": "^0.3.0",
551
- "@remix-run/cli": "^0.6.0",
552
556
  "@remix-run/session-storage-redis": "^0.1.1",
553
- "@remix-run/route-pattern": "^0.24.0",
554
- "@remix-run/render-middleware": "^0.2.0",
555
- "@remix-run/ui-hmr": "^0.1.0",
556
- "@remix-run/node-hmr": "^0.1.0",
557
- "@remix-run/spa": "^0.1.0",
558
- "@remix-run/static-middleware": "^0.4.14",
557
+ "@remix-run/test": "^0.6.0",
558
+ "@remix-run/cli": "^0.7.0",
559
+ "@remix-run/tar-parser": "^0.7.1",
560
+ "@remix-run/spa": "^0.1.1",
561
+ "@remix-run/static-middleware": "^0.4.15",
562
+ "@remix-run/render-middleware": "^0.3.0",
559
563
  "@remix-run/terminal": "^0.1.1",
560
- "@remix-run/session-storage-memcache": "^0.1.2",
561
- "@remix-run/test": "^0.6.0"
564
+ "@remix-run/multiple-import-maps-polyfill": "^0.1.0",
565
+ "@remix-run/ui-hmr": "^0.1.0",
566
+ "@remix-run/node-hmr": "^0.2.0"
562
567
  },
563
568
  "bin": {
564
569
  "remix": "./dist/cli-entry.js"
@@ -10,7 +10,7 @@ Fetch-based server for compiling browser assets on demand.
10
10
  - **Preloads** - Generate preload URLs for scripts and styles based on imports
11
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
- - **Optional Fingerprinting** - Source-based fingerprinted URLs for long-lived browser caching
13
+ - **Optional Fingerprinting** - Content-based fingerprinted URLs for long-lived browser caching
14
14
  - **Source Maps** - Serve inline or external sourcemaps
15
15
  - **Hot Module Reloading** - Handle live code updates in development
16
16
  - **Script Loaders** - Post-process compiled JavaScript with Node-compatible loaders
@@ -164,6 +164,8 @@ let assetServer = createAssetServer({
164
164
  })
165
165
  ```
166
166
 
167
+ Package managers that install outside `rootDir`, such as pnpm's global virtual store, are handled without configuration: the asset server reads the store location from the nearest `node_modules/.modules.yaml` and mounts it internally so those package files still resolve to public URLs. A store that a configured mount already covers, such as pnpm's default `node_modules/.pnpm`, is left alone.
168
+
167
169
  ### File watching
168
170
 
169
171
  The file system is watched by default so source changes are picked up without requiring a server restart.
@@ -212,13 +214,68 @@ let assetServer = createAssetServer({
212
214
  })
213
215
  ```
214
216
 
217
+ ## Script Entries
218
+
219
+ Use `assetServer.getScriptEntry()` to get everything needed to load a script and its dependencies.
220
+
221
+ ```ts
222
+ let { href, importMap, preloads } = await assetServer.getScriptEntry('app/assets/entry.tsx')
223
+ ```
224
+
225
+ This can be used when rendering a document shell:
226
+
227
+ ```tsx
228
+ import type { Handle, RemixNode } from 'remix/ui'
229
+ import { ImportMap } from 'remix/ui/server'
230
+ import { assetServer } from './assets.ts'
231
+
232
+ let { href, importMap, preloads } = await assetServer.getScriptEntry('app/assets/entry.tsx')
233
+
234
+ export function Document(handle: Handle<{ children: RemixNode }>) {
235
+ return () => (
236
+ <html>
237
+ <head>
238
+ {/* ... */}
239
+ <ImportMap value={importMap} />
240
+ {preloads.map((preload) => (
241
+ <link rel="modulepreload" href={preload} />
242
+ ))}
243
+ <script type="module" src={href} />
244
+ </head>
245
+ <body>{handle.props.children}</body>
246
+ </html>
247
+ )
248
+ }
249
+ ```
250
+
251
+ This can also be used for resolved client entries in [`remix/ui`](https://github.com/remix-run/remix/tree/main/packages/ui) when using `import.meta.url` as the client entry ID:
252
+
253
+ ```tsx
254
+ import { renderToStream } from 'remix/ui/server'
255
+ import { assetServer } from './assets.ts'
256
+
257
+ let stream = renderToStream(<App />, {
258
+ async resolveClientEntry(entryId, component) {
259
+ let { href, importMap, preloads } = await assetServer.getScriptEntry(entryId)
260
+
261
+ return {
262
+ href,
263
+ importMap,
264
+ preloads,
265
+ exportName: entryId.split('#')[1] || component.name,
266
+ }
267
+ },
268
+ // ...
269
+ })
270
+ ```
271
+
215
272
  ## Hrefs
216
273
 
217
274
  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.
218
275
 
219
276
  ```ts
220
- let src = await assetServer.getHref('app/actions/public/entry.ts')
221
- // '/assets/app/actions/public/entry.ts'
277
+ let src = await assetServer.getHref('app/media/public/logo.svg')
278
+ // '/assets/app/media/public/logo.svg'
222
279
  ```
223
280
 
224
281
  ## Inspection
@@ -244,6 +301,16 @@ let src = await assetServer.getHref('app/media/public/image.png', {
244
301
  // '/assets/app/media/public/image.png?transform=resize%3A100x100&transform=webp'
245
302
  ```
246
303
 
304
+ ## Import Maps
305
+
306
+ Scripts retain their imports as authored and rely on import maps for resolution in the browser. `assetServer.getScriptEntry()` returns the import map for a single rendered script entry. Use `assetServer.getImportMap()` directly when you need to generate a combined import map for multiple script roots or other custom graph-level behavior.
307
+
308
+ ```ts
309
+ let importMap = await assetServer.getImportMap(['app/assets/entry.tsx', 'app/assets/search.tsx'])
310
+ ```
311
+
312
+ Without fingerprinting, import maps resolve authored specifiers to stable asset URLs. With fingerprinting enabled, the same import maps resolve stable asset URLs to content-fingerprinted asset URLs.
313
+
247
314
  ## Preloads
248
315
 
249
316
  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.
@@ -266,7 +333,7 @@ let preloads = await assetServer.getPreloads([
266
333
 
267
334
  By default, assets are served at stable URLs with ETags and `Cache-Control: no-cache`.
268
335
 
269
- If you want clients to cache assets aggressively without revalidation, you can opt into source-based fingerprinting.
336
+ If you want clients to cache assets aggressively without revalidation, you can opt into content-based fingerprinting.
270
337
 
271
338
  ```ts
272
339
  import { createAssetServer } from 'remix/assets'
@@ -276,15 +343,13 @@ let assetServer = createAssetServer({
276
343
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
277
344
  allowPackages: ['remix'],
278
345
  watch: false,
279
- fingerprint: {
280
- buildId: process.env.GITHUB_SHA,
281
- },
346
+ fingerprint: true,
282
347
  })
283
348
  ```
284
349
 
285
350
  When fingerprinting is enabled, assets use a `.@<fingerprint>` segment before the file extension and are served with `Cache-Control: public, max-age=31536000, immutable`.
286
351
 
287
- 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`.
352
+ Fingerprints are based on emitted asset contents. This allows unchanged assets to keep the same URL across deployments, but it assumes that files on disk won't change after a URL is generated, so fingerprinting requires `watch: false`.
288
353
 
289
354
  ## Target
290
355
 
@@ -542,11 +607,9 @@ let assetServer = createAssetServer({
542
607
 
543
608
  #### File transform caching
544
609
 
545
- Use `files.cache` to store transformed file outputs via a [`file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage) backend.
546
-
547
- Without `files.cache`, transformed file outputs are recomputed per request.
610
+ Use `files.cache` to store transformed file outputs via a [`file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage) backend. Without `files.cache`, transformed file outputs are recomputed per request.
548
611
 
549
- If `fingerprint.buildId` is set, the file cache can be reused across server restarts for the same build.
612
+ `files.cacheKey` scopes transformed file cache entries. Use a stable identifier, such as a commit SHA, when you want unchanged transformed files to be reused across server restarts for the same build.
550
613
 
551
614
  ```ts
552
615
  import * as path from 'node:path'
@@ -559,6 +622,7 @@ let assetServer = createAssetServer({
559
622
  allowPackages: ['remix'],
560
623
  files: {
561
624
  cache: createFsFileStorage(path.resolve('.tmp/assets-cache')),
625
+ cacheKey: process.env.GIT_COMMIT_SHA,
562
626
  extensions: ['.svg', '.png', '.jpg', '.jpeg', '.woff2'],
563
627
  transforms: {
564
628
  /*...*/
@@ -647,7 +711,7 @@ let isDevelopment = process.env.NODE_ENV === 'development'
647
711
  let assetServer = createAssetServer({
648
712
  basePath: '/assets',
649
713
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
650
- denyFiles: ['app/**/*.test.*'],
714
+ allowPackages: ['remix'],
651
715
  hmr: isDevelopment
652
716
  ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
653
717
  : undefined,
@@ -655,6 +719,30 @@ let assetServer = createAssetServer({
655
719
  })
656
720
  ```
657
721
 
722
+ Use `moduleImporter` to customize how HMR dynamically imports updated browser modules. It is resolved relative to the asset server's root directory and must point to a browser module exporting:
723
+
724
+ ```ts
725
+ export function importModule(specifier: string, parentUrl: string): Promise<Record<string, unknown>>
726
+ ```
727
+
728
+ HMR appends mappings for updated modules to the document in additional `<script type="importmap">` elements. Use `remix/multiple-import-maps-polyfill` when these updates must work in browsers without native support for multiple import maps:
729
+
730
+ ```ts
731
+ import { createAssetServer } from 'remix/assets'
732
+ import { createBrowserHmrChannel } from 'remix/node-hmr/runtime'
733
+
734
+ let assetServer = createAssetServer({
735
+ basePath: '/assets',
736
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
737
+ allowPackages: ['remix'],
738
+ hmr: {
739
+ channel: createBrowserHmrChannel,
740
+ moduleImporter: 'remix/multiple-import-maps-polyfill',
741
+ },
742
+ watch: true,
743
+ })
744
+ ```
745
+
658
746
  ### `import.meta.hot`
659
747
 
660
748
  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.
@@ -237,6 +237,45 @@ router.map(routes.contact, {
237
237
  })
238
238
  ```
239
239
 
240
+ #### Method Not Allowed Responses
241
+
242
+ When a request URL matches one or more route patterns, but none of those routes are registered for the request method, the router first falls through to any less specific route that can handle the method (including `ANY` routes). If no route can handle the request, the router responds with `405 Method Not Allowed` and an [`Allow` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Allow) listing the methods that are registered for that URL, instead of treating the request as a 404.
243
+
244
+ ```ts
245
+ import { createRouter } from 'remix/router'
246
+
247
+ let router = createRouter()
248
+
249
+ router.get('/users/:id', ({ params }) => new Response(`User ${params.id}`))
250
+ router.put('/users/:id', ({ params }) => new Response(`Updated ${params.id}`))
251
+
252
+ let response = await router.fetch('https://remix.run/users/1', { method: 'DELETE' })
253
+ // 405 Method Not Allowed
254
+ // Allow: GET, HEAD, PUT
255
+ ```
256
+
257
+ If you want a handler to receive every request method at a given URL instead, register an `ANY` route for it.
258
+
259
+ #### HEAD Requests
260
+
261
+ Routes registered for `GET` also serve `HEAD` requests. The `GET` handler runs as usual, and the router strips the body from its response, so a `HEAD` request observes the same status and headers as the equivalent `GET` request.
262
+
263
+ ```ts
264
+ let router = createRouter()
265
+
266
+ router.get(
267
+ '/report',
268
+ () => new Response('report data', { headers: { 'Content-Type': 'text/csv' } }),
269
+ )
270
+
271
+ let response = await router.fetch('https://remix.run/report', { method: 'HEAD' })
272
+ // 200 OK
273
+ // Content-Type: text/csv
274
+ // (empty body)
275
+ ```
276
+
277
+ If you register an explicit `HEAD` route for the same pattern, it takes precedence over the `GET` route for `HEAD` requests.
278
+
240
279
  ### Composing Route Groups
241
280
 
242
281
  As applications grow, it is useful to let one file own the routes for a specific area of the app while the top-level router decides where that area is mounted. Use `router.mount()` with a route installer to register a group of routes under a route pattern prefix.
@@ -0,0 +1,107 @@
1
+ # multiple-import-maps-polyfill
2
+
3
+ Polyfill for dynamic JavaScript imports that depend on import maps added after the document's initial import map. Browsers with native support for multiple import maps continue to use native dynamic imports.
4
+
5
+ The module loading logic in this package was adapted from [ES Module Shims](https://github.com/guybedford/es-module-shims) by Guy Bedford.
6
+
7
+ This package assumes the initial document contains one complete import map before any module scripts. It is designed to load dynamic imports that depend on additional import maps installed after the initial page load.
8
+
9
+ ## Features
10
+
11
+ - Detects native support for multiple import maps
12
+ - Loads dynamic imports through every import map in the document when a polyfill is required
13
+ - Preloads dynamic modules through the same polyfill cache
14
+ - Resolves native module types through import maps while leaving their loading semantics to the browser
15
+ - Supports import-map integrity metadata
16
+
17
+ ## Installation
18
+
19
+ ```sh
20
+ npm i remix
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ Use `importModule` in place of `import()` when a dynamic import may depend on an import map added at runtime:
26
+
27
+ ```ts
28
+ import { importModule } from 'remix/multiple-import-maps-polyfill'
29
+
30
+ let moduleUrl = new URL('./features/search.ts', import.meta.url).href
31
+ let feature = await importModule(moduleUrl)
32
+ feature.openSearch()
33
+ ```
34
+
35
+ `importModule` uses native `import()` when the browser supports multiple import maps. In other browsers, it loads the module through the polyfill using every import map currently installed in the document.
36
+
37
+ Use `detectMultipleImportMapSupport` and `preloadShim` when an integration also manages module preloads. For example, configure Remix UI to load client entries discovered during navigation:
38
+
39
+ ```ts
40
+ import {
41
+ detectMultipleImportMapSupport,
42
+ importModule,
43
+ preloadShim,
44
+ } from 'remix/multiple-import-maps-polyfill'
45
+ import { run } from 'remix/ui'
46
+
47
+ run({
48
+ async loadModule(moduleUrl, exportName) {
49
+ let module = await importModule(moduleUrl)
50
+ let Component = module[exportName]
51
+ if (typeof Component !== 'function') {
52
+ throw new Error(`Unknown component: ${moduleUrl}#${exportName}`)
53
+ }
54
+ return Component
55
+ },
56
+ async processClientEntryPreloads(preloads) {
57
+ if (await detectMultipleImportMapSupport()) return preloads
58
+
59
+ preloadShim(preloads)
60
+ return []
61
+ },
62
+ })
63
+ ```
64
+
65
+ Browsers with multiple import map support retain native module loading and native `<link rel="modulepreload">` elements. Other browsers load late client entries and preloads through the polyfill.
66
+
67
+ `detectMultipleImportMapSupport()` returns a cached promise. When it detects that the polyfill is required, it begins loading the polyfill runtime in the background so the work can overlap with a later `importShim()` or `preloadShim()` call. Browsers with multiple import map support do not load the polyfill runtime.
68
+
69
+ `importShim()` always uses the polyfill to load a dynamic JavaScript import using every import map currently installed in the document. It does not detect native support. `preloadShim()` always uses the polyfill fetch cache rather than native module preload links. Like native module preloads, it fetches only the supplied module specifier or array of specifiers and caches their responses for later imports. Preload failures are ignored. `importShim()` reports the failure if the module is later required.
70
+
71
+ ## Content Security Policy
72
+
73
+ Polyfilled module graphs are evaluated from blob URLs, and `es-module-lexer` compiles its parser from Wasm. Content Security Policies must allow `blob:` module scripts and Wasm compilation with `'wasm-unsafe-eval'`.
74
+
75
+ The support detector creates a Trusted Types policy named `remix/multiple-import-maps-polyfill`. Allow this policy when Trusted Types are required for scripts, for example:
76
+
77
+ ```http
78
+ Content-Security-Policy: script-src 'self' blob: 'wasm-unsafe-eval'; require-trusted-types-for 'script'; trusted-types remix/multiple-import-maps-polyfill;
79
+ ```
80
+
81
+ ## `remix/assets` HMR Support
82
+
83
+ HMR appends mappings for updated modules to the document in additional `<script type="importmap">` elements. Configure the Remix asset server's `hmr.moduleImporter` option to use the polyfilled `importModule` function when these updates must work in browsers without native support for multiple import maps:
84
+
85
+ ```ts
86
+ import { createAssetServer } from 'remix/assets'
87
+ import { createBrowserHmrChannel } from 'remix/node-hmr/runtime'
88
+
89
+ let assets = createAssetServer({
90
+ hmr: {
91
+ channel: createBrowserHmrChannel,
92
+ moduleImporter: 'remix/multiple-import-maps-polyfill',
93
+ },
94
+ watch: true,
95
+ })
96
+ ```
97
+
98
+ The module importer and its dependencies must be available through the document's initial import map. Importing `remix/multiple-import-maps-polyfill` from the application's main client entry satisfies this requirement.
99
+
100
+ ## Related Packages
101
+
102
+ - [`assets`](https://github.com/remix-run/remix/tree/main/packages/assets) - Compiles and serves browser assets
103
+ - [`ui`](https://github.com/remix-run/remix/tree/main/packages/ui) - Loads client entries discovered during navigation
104
+
105
+ ## License
106
+
107
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/packages/multiple-import-maps-polyfill/LICENSE)
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/multiple-import-maps-polyfill'
@@ -6,7 +6,7 @@ Request-scoped response rendering for Remix. It provides the conventional Remix
6
6
 
7
7
  - **Remix UI rendering** - Stream nodes to HTML responses with `render()`
8
8
  - **Framework-owned frames** - Resolve nested and targeted `<Frame>` requests through the current router
9
- - **Client entry assets** - Resolve source-based `clientEntry()` modules and their preloads through an asset server
9
+ - **Client entry assets** - Resolve source-based `clientEntry()` modules, import maps, and preloads through an asset server
10
10
  - **Typed context** - Preserve renderer input and response option types on `context.render`
11
11
  - **Custom renderers** - Install JSON, email, or other response pipelines with `renderWith()`
12
12
 
@@ -72,7 +72,7 @@ The middleware forwards request credentials and session headers to internal fram
72
72
 
73
73
  ### Options
74
74
 
75
- - **`assets`** - An asset server that resolves source-based client entry IDs to browser module URLs and preload URLs. Omit it when client entries already use public URLs or the app has no client entries.
75
+ - **`assets`** - An asset server that resolves source-based client entry IDs to browser module URLs, import maps, and preload URLs. Omit it when client entries already use public URLs or the app has no client entries.
76
76
  - **`onError`** - A callback for server rendering errors. When omitted, the UI renderer uses its default error reporting.
77
77
 
78
78
  ## Custom renderers
package/src/ui/README.md CHANGED
@@ -79,10 +79,9 @@ function Actions() {
79
79
  }
80
80
  ```
81
81
 
82
- ## Frame Navigation
82
+ ## Client Entry Loading
83
83
 
84
- `run()` progressively enhances same-origin links and forms using a default `resolveFrame` that
85
- fetches the frame source:
84
+ `run()` hydrates client entries by calling `loadModule` for each component module:
86
85
 
87
86
  ```tsx
88
87
  import { run } from 'remix/ui'
@@ -97,6 +96,47 @@ let app = run({
97
96
  await app.ready()
98
97
  ```
99
98
 
99
+ Client entries introduced after the initial document may depend on import maps added at runtime.
100
+ When targeting browsers without native support for multiple import maps, use
101
+ `remix/multiple-import-maps-polyfill` to load these modules and process their preloads:
102
+
103
+ ```tsx
104
+ import {
105
+ detectMultipleImportMapSupport,
106
+ importModule,
107
+ preloadShim,
108
+ } from 'remix/multiple-import-maps-polyfill'
109
+
110
+ let app = run({
111
+ async loadModule(moduleUrl, exportName) {
112
+ let module = await importModule(moduleUrl)
113
+ let Component = module[exportName]
114
+ if (typeof Component !== 'function') {
115
+ throw new Error(`Unknown component: ${moduleUrl}#${exportName}`)
116
+ }
117
+ return Component
118
+ },
119
+ async processClientEntryPreloads(preloads) {
120
+ if (await detectMultipleImportMapSupport()) return preloads
121
+
122
+ preloadShim(preloads)
123
+ return []
124
+ },
125
+ })
126
+ ```
127
+
128
+ ## Frame Navigation
129
+
130
+ The same runtime represents the current document as `app.frames.top` and intercepts eligible
131
+ same-origin links and forms through the browser's Navigation API. Those navigations fetch HTML with
132
+ the frame resolver and update the existing document in place instead of loading a new document.
133
+ This soft-navigation behavior applies even when the page only uses `clientEntry()` and does not
134
+ render an explicit `<Frame>`.
135
+
136
+ Frame navigation requires both `window.navigation` and `NavigateEvent.sourceElement`. Browsers
137
+ missing either capability use document navigation for links, forms, and `navigate()`. Hydration and
138
+ explicit frame reloads still work.
139
+
100
140
  The default resolver is equivalent to:
101
141
 
102
142
  ```js
@@ -108,7 +148,8 @@ async function resolveFrame(src, options) {
108
148
  signal: options?.signal,
109
149
  })
110
150
 
111
- if (!response.ok) {
151
+ let isHtml = response.headers.get('Content-Type')?.toLowerCase().includes('text/html')
152
+ if (response.status >= 500 || (response.status >= 300 && !isHtml)) {
112
153
  throw new Error(`Failed to resolve frame: ${response.status} ${response.statusText}`.trimEnd())
113
154
  }
114
155
 
@@ -149,11 +190,18 @@ CRLF-delimited text, and `multipart/form-data` submissions use `FormData`. Pass
149
190
  `resolveFrame` when the server requires additional headers, another body encoding, or a different
150
191
  response policy.
151
192
 
152
- Add `data-rmx-document` to a link or form to leave its navigation to the browser.
193
+ Add `data-rmx-document` to a link or form to leave that navigation to the browser. To keep all links
194
+ and forms as document navigations while still hydrating client entries and using explicit frames,
195
+ register a listener before calling `run()`:
196
+
197
+ ```ts
198
+ window.navigation?.addEventListener('navigate', (e) => e.stopImmediatePropagation())
199
+ ```
200
+
201
+ This prevents Remix from intercepting Navigation API events. Explicit frame reloads such as
202
+ `handle.frame.reload()` continue to use the frame resolver.
153
203
 
154
- The default resolver rejects non-OK responses with an error containing their status and status text.
155
- A custom `resolveFrame` may return a `Response` with any status when it wants Remix UI to render the
156
- response body.
204
+ The default resolver accepts `2xx` responses and `3xx` or `4xx` responses whose `Content-Type` includes `text/html`, ignoring case. It rejects other `3xx` or `4xx` responses and all `5xx` responses with an error containing their status and status text. A custom `resolveFrame` may return a `Response` with any status when it wants Remix UI to render the response body.
157
205
 
158
206
  Forms remain ordinary HTML forms before the runtime starts. Add `data-rmx-target` to reload a named frame, or `data-rmx-document` to require a full-document submission:
159
207
 
@@ -33,12 +33,10 @@ let stream = renderToStream(<App />, {
33
33
  return fetchHtml(frameUrl)
34
34
  },
35
35
  async resolveClientEntry(entryId, component) {
36
- let [href, preloads] = await Promise.all([
37
- assetServer.getHref(entryId),
38
- assetServer.getPreloads(entryId),
39
- ])
36
+ let { href, importMap, preloads } = await assetServer.getScriptEntry(entryId)
40
37
  return {
41
38
  href,
39
+ importMap,
42
40
  exportName: entryId.split('#')[1] || component.name,
43
41
  preloads,
44
42
  }
@@ -59,7 +57,7 @@ return new Response(stream, {
59
57
  - **`topFrameSrc`** - Overrides the root frame URL used for `handle.frames.top.src`. This is mainly useful when calling `renderToStream()` from inside `resolveFrame()` for a nested frame render.
60
58
  - **`signal`** - Cancels pending server rendering work. Pass `request.signal` so client disconnects can stop unresolved frame work without invoking `onError` for the disconnect itself.
61
59
  - **`resolveFrame(src, target, context)`** - Called when a `<Frame>` needs its content. Return a string of HTML, a `ReadableStream<Uint8Array>`, or a promise of either. `context.currentFrameSrc` is the URL for the frame that contains the `<Frame>`, and `context.topFrameSrc` is the outer document URL. Required if your component tree contains `<Frame>` elements.
62
- - **`resolveClientEntry(entryId, component)`** - Resolves the public module URL, export name, and optional module preload hrefs for a hydrated client entry.
60
+ - **`resolveClientEntry(entryId, component)`** - Resolves the public module URL, export name, optional import map, and optional module preload hrefs for a hydrated client entry.
63
61
  - **`onError(error)`** - Called when a rendering error occurs. If not provided, the stream rejects with the error.
64
62
 
65
63
  When you render nested frame responses with `renderToStream()` inside `resolveFrame()`, pass `frameSrc` for the frame being rendered and carry `topFrameSrc` forward from the parent context. That preserves `handle.frames.top.src` across the whole SSR frame tree.
@@ -97,6 +95,28 @@ function ProductPage() {
97
95
 
98
96
  Components using the `css(...)` mixin through `mix` have their styles collected during rendering and emitted as a single `<style>` tag in the `<head>`. No client-side style injection is needed for server-rendered content.
99
97
 
98
+ ### Import maps
99
+
100
+ Use `<ImportMap>` when the document needs one initial import map containing both authored mappings and mappings from blocking client entries. The server merges these mappings into the component before sending the initial HTML.
101
+
102
+ Regular `<script type="importmap">` elements remain supported. The server leaves them unchanged and emits any additional client entry mappings separately, omitting entries already present in authored maps.
103
+
104
+ ```tsx
105
+ import type { Handle, RemixNode } from 'remix/ui'
106
+ import { ImportMap } from 'remix/ui/server'
107
+
108
+ function Document(handle: Handle<{ children: RemixNode }>) {
109
+ return () => (
110
+ <html>
111
+ <head>
112
+ <ImportMap value={scriptEntry.importMap} />
113
+ </head>
114
+ <body>{handle.props.children}</body>
115
+ </html>
116
+ )
117
+ }
118
+ ```
119
+
100
120
  ## See Also
101
121
 
102
122
  - [Hydration](https://github.com/remix-run/remix/blob/main/packages/ui/docs/hydration.md) - Making server-rendered components interactive on the client
@@ -39,7 +39,10 @@ let assetServer = createAssetServer({
39
39
  allowPackages: ['remix'],
40
40
  denyFiles: ['app/**/*.test.*'],
41
41
  hmr: isDevelopment
42
- ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
42
+ ? {
43
+ channel: async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel(),
44
+ moduleImporter: 'remix/multiple-import-maps-polyfill',
45
+ }
43
46
  : undefined,
44
47
  scripts: {
45
48
  loaders: isDevelopment ? [uiHmr()] : undefined,
@@ -48,6 +51,8 @@ let assetServer = createAssetServer({
48
51
  })
49
52
  ```
50
53
 
54
+ HMR appends mappings for updated modules to the document in additional `<script type="importmap">` elements. The module importer allows browsers without native support for multiple import maps to load these updates.
55
+
51
56
  ## Direct Transforms
52
57
 
53
58
  Use the direct transform APIs when you are writing your own loader, Node module hooks, or build integration.