@remix-run/cli 0.6.0 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Command-line interface for Remix",
5
5
  "author": "Michael Jackson <mjijackson@gmail.com>",
6
6
  "license": "MIT",
@@ -33,13 +33,13 @@
33
33
  "dependencies": {
34
34
  "jsonc-parser": "^3.3.1",
35
35
  "semver": "^7.7.4",
36
- "@remix-run/data-table": "^0.5.0",
37
- "@remix-run/data-table-postgres": "^0.5.1",
38
- "@remix-run/data-table-mysql": "^0.5.1",
39
- "@remix-run/data-table-sqlite": "^0.6.1",
40
- "@remix-run/assets": "^0.6.0",
41
- "@remix-run/test": "^0.6.0",
42
- "@remix-run/terminal": "^0.1.1"
36
+ "@remix-run/assets": "^0.7.0",
37
+ "@remix-run/data-table": "^0.5.1",
38
+ "@remix-run/data-table-postgres": "^0.5.2",
39
+ "@remix-run/data-table-mysql": "^0.5.2",
40
+ "@remix-run/data-table-sqlite": "^0.6.2",
41
+ "@remix-run/terminal": "^0.1.1",
42
+ "@remix-run/test": "^0.6.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^24.6.0",
@@ -5,7 +5,7 @@
5
5
  How to serve browser scripts and styles from source. Read this when the task involves:
6
6
 
7
7
  - Configuring `createAssetServer` (`basePath`, `mounts`, `allowFiles`, `allowPackages`, `denyFiles`, fingerprinting, compiler options)
8
- - Choosing between `staticFiles()` for already-built files and `createAssetServer()` for source assets that need import rewriting, preloads, or fingerprinted URLs
8
+ - Choosing between `staticFiles()` for already-built files and `createAssetServer()` for source assets that need dependency resolution, preloads, or fingerprinted URLs
9
9
  - Generating script URLs or `<link rel="modulepreload">` tags for a client entry
10
10
  - Enabling browser HMR for source-served modules
11
11
  - Keeping files such as tests out of the browser via `denyFiles` rules
@@ -16,7 +16,7 @@ For routing the URL namespace itself, see `routing-and-controllers.md`. For clie
16
16
 
17
17
  Use `remix/assets` when the app serves browser JavaScript, TypeScript, or CSS from source files. This is the right tool for client entrypoints, browser-only helpers, styles, and monorepo code that should be compiled and served under a public URL namespace.
18
18
 
19
- Use `staticFiles()` for files that already exist on disk exactly as they should be served. Use `createAssetServer()` for source scripts or styles that need rewriting, dependency scanning, preloads, sourcemaps, or fingerprinted URLs.
19
+ Use `staticFiles()` for files that already exist on disk exactly as they should be served. Use `createAssetServer()` for source scripts or styles that need compilation, dependency scanning, preloads, sourcemaps, or fingerprinted URLs.
20
20
 
21
21
  ## Default Pattern
22
22
 
@@ -71,16 +71,17 @@ export default createController(routes, {
71
71
 
72
72
  ## Rendering HTML
73
73
 
74
- Use `getHref()` when you need the public URL for one module, and `getPreloads()` when you want `<link rel="modulepreload">` tags or `Link` headers for one or more entrypoints and their dependencies.
74
+ Use `getScriptEntry()` when rendering a browser script entry. Scripts keep JavaScript imports as authored, so a rendered script entry needs its public URL, modulepreload hints, and an import map.
75
75
 
76
76
  ```typescript
77
- let entryHref = await assets.getHref('app/actions/public/entry.ts')
78
- let entryPreloads = await assets.getPreloads('app/actions/public/entry.ts')
77
+ let { href, importMap, preloads } = await assets.getScriptEntry('app/actions/public/entry.ts')
79
78
  ```
80
79
 
81
- Use this when rendering documents or layouts that boot browser behavior with a known client entry.
80
+ Render `importMap` with `ImportMap` from `remix/ui/server` before the modulepreload links and module script. This combines its mappings with import maps from blocking client entries.
82
81
 
83
- For normal Remix applications, pass the asset server to `render({ assets })` from `remix/middleware/render`. The middleware resolves source entry IDs from `clientEntry(import.meta.url, ...)` with `getHref()` and `getPreloads()` and applies the UI renderer's explicit-hash or named-component export rules. Use a custom `resolveClientEntry` callback only when building a custom rendering pipeline.
82
+ Use `getHref()` directly when you need the public URL for a non-script asset, and `getPreloads()` when you need lower-level preload control for one or more entrypoints.
83
+
84
+ For normal Remix applications, pass the asset server to `render({ assets })` from `remix/middleware/render`. The middleware resolves source entry IDs from `clientEntry(import.meta.url, ...)` with `getScriptEntry()`, includes their import maps, and applies the UI renderer's explicit-hash or named-component export rules. Use a custom `resolveClientEntry` callback only when building a custom rendering pipeline.
84
85
 
85
86
  ## Development vs Deployment
86
87
 
@@ -91,12 +92,15 @@ In development:
91
92
  - Enable source maps when debugging browser code
92
93
  - Use `hmr` only when the app is running under `remix/node-hmr`
93
94
  - Use `scripts.loaders` for development-only browser transforms such as `uiHmr()`
95
+ - Configure `hmr.moduleImporter` with `remix/multiple-import-maps-polyfill` when HMR must support browsers without native support for multiple import maps. HMR appends updated mappings in additional `<script type="importmap">` elements
94
96
 
95
97
  In deployment:
96
98
 
97
99
  - Set `watch: false`
98
- - Use `fingerprint: { buildId }` for long-lived immutable caching
99
- - Make sure `buildId` changes for each deploy
100
+ - Use `fingerprint: true` for content-based fingerprints and long-lived immutable caching
101
+ - Render the script import map before modulepreload links and module scripts
102
+ - Keep bare-import resolution uniform for files in the same directory; different directories may use
103
+ different resolutions through more-specific import map scopes
100
104
 
101
105
  Fingerprinting assumes files on disk are stable and requires `watch: false`.
102
106
 
@@ -117,7 +121,10 @@ const assetServer = createAssetServer({
117
121
  denyFiles: ['app/**/*.test.*'],
118
122
  watch: isDevelopment,
119
123
  hmr: isHmr
120
- ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
124
+ ? {
125
+ channel: async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel(),
126
+ moduleImporter: 'remix/multiple-import-maps-polyfill',
127
+ }
121
128
  : undefined,
122
129
  scripts: {
123
130
  loaders: isHmr ? [uiHmr()] : undefined,
@@ -88,6 +88,42 @@ The resolved `preloads` array contains browser module hrefs. During server rende
88
88
 
89
89
  Use `run` to start the client runtime. It scans the document for client entry markers, loads modules, and hydrates each one:
90
90
 
91
+ Client entries introduced by later frame responses may depend on import map entries that were not in the initial document. Browsers without native support for multiple import maps cannot resolve those modules with `import()`. Apps that use asset server import maps and target these browsers can opt into `remix/multiple-import-maps-polyfill`:
92
+
93
+ ```tsx
94
+ import {
95
+ detectMultipleImportMapSupport,
96
+ importModule,
97
+ preloadShim,
98
+ } from 'remix/multiple-import-maps-polyfill'
99
+ import { run } from 'remix/ui'
100
+
101
+ const app = run({
102
+ async loadModule(moduleUrl, exportName) {
103
+ let mod = await importModule(moduleUrl)
104
+ let Component = mod[exportName]
105
+ if (typeof Component !== 'function') {
106
+ throw new Error(`Unknown component: ${moduleUrl}#${exportName}`)
107
+ }
108
+ return Component
109
+ },
110
+ async processClientEntryPreloads(preloads) {
111
+ if (await detectMultipleImportMapSupport()) return preloads
112
+
113
+ preloadShim(preloads)
114
+ return []
115
+ },
116
+ })
117
+
118
+ app.addEventListener('error', (event) => {
119
+ console.error('Component error:', event.error)
120
+ })
121
+
122
+ await app.ready()
123
+ ```
124
+
125
+ The support check keeps native imports and modulepreload links in browsers that support multiple import maps. Other browsers use the polyfill for late client entries and their preloads. Apps that do not need this compatibility can use `import()` directly in `loadModule` and omit `processClientEntryPreloads`.
126
+
91
127
  ```tsx
92
128
  import { run } from 'remix/ui'
93
129
 
@@ -69,7 +69,7 @@ let router = createRouter({ middleware })
69
69
  ### Static files vs browser modules
70
70
 
71
71
  - Use `staticFiles()` for files that should be served directly from disk, such as images, fonts, or already-built assets in the root `public/` directory
72
- - Use `remix/assets` when browser modules should be compiled and served from source files with import rewriting, preloads, or fingerprinted URLs
72
+ - Use `remix/assets` when browser modules should be compiled and served from source files with dependency resolution, preloads, or fingerprinted URLs
73
73
  - `public/` directories inside `app/` hold browser-reachable source for the asset server
74
74
 
75
75
  ### Ordering notes
@@ -1,7 +1,8 @@
1
1
  import type { Handle, RemixNode } from 'remix/ui'
2
2
  import { css } from 'remix/ui'
3
+ import { ImportMap } from 'remix/ui/server'
3
4
 
4
- import { entryHref, entryPreloads } from '../assets.ts'
5
+ import { scriptEntry } from '../assets.ts'
5
6
 
6
7
  export interface DocumentProps {
7
8
  children?: RemixNode
@@ -14,6 +15,7 @@ const DEFAULT_TITLE = readAppDisplayName('%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%
14
15
  export function Document(handle: Handle<DocumentProps>) {
15
16
  return () => {
16
17
  let { children, head, title = DEFAULT_TITLE } = handle.props
18
+ let { href, importMap, preloads } = scriptEntry
17
19
 
18
20
  return (
19
21
  <html lang="en">
@@ -24,10 +26,11 @@ export function Document(handle: Handle<DocumentProps>) {
24
26
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
25
27
  <title>{title}</title>
26
28
  {head}
27
- {entryPreloads.map((href) => (
28
- <link key={href} rel="modulepreload" href={href} />
29
+ <ImportMap value={importMap} />
30
+ {preloads.map((preloadHref) => (
31
+ <link key={preloadHref} rel="modulepreload" href={preloadHref} />
29
32
  ))}
30
- <script type="module" src={entryHref}></script>
33
+ <script type="module" src={href}></script>
31
34
  </head>
32
35
  <body mix={css({ margin: 0 })}>{children}</body>
33
36
  </html>
@@ -1,23 +1,24 @@
1
+ import {
2
+ detectMultipleImportMapSupport,
3
+ importModule,
4
+ preloadShim,
5
+ } from 'remix/multiple-import-maps-polyfill'
1
6
  import { run } from 'remix/ui'
2
7
 
3
8
  const app = run({
4
9
  async loadModule(moduleUrl, exportName) {
5
- let mod = await import(moduleUrl)
6
- return mod[exportName]
7
- },
8
- async resolveFrame(src, options) {
9
- let response = await fetch(src, {
10
- headers: { Accept: 'text/html' },
11
- method: options?.method,
12
- body: getRequestBody(options?.formData, options?.method, options?.encType),
13
- signal: options?.signal,
14
- })
15
- if (!response.ok) {
16
- return `<pre>Frame error: ${response.status} ${response.statusText}</pre>`
10
+ let mod = await importModule(moduleUrl)
11
+ let Component = mod[exportName]
12
+ if (typeof Component !== 'function') {
13
+ throw new Error(`Unknown component: ${moduleUrl}#${exportName}`)
17
14
  }
15
+ return Component
16
+ },
17
+ async processClientEntryPreloads(preloads) {
18
+ if (await detectMultipleImportMapSupport()) return preloads
18
19
 
19
- if (response.body) return response.body
20
- return await response.text()
20
+ preloadShim(preloads)
21
+ return []
21
22
  },
22
23
  })
23
24
 
@@ -31,18 +32,3 @@ if (import.meta.hot) {
31
32
  }
32
33
  })
33
34
  }
34
-
35
- function getRequestBody(
36
- formData?: FormData,
37
- method?: string,
38
- encType?: string,
39
- ): BodyInit | undefined {
40
- if (!formData || method?.toLowerCase() === 'get') return
41
- if (encType !== 'application/x-www-form-urlencoded') return formData
42
-
43
- let body = new URLSearchParams()
44
- for (let [name, value] of formData) {
45
- body.append(name, typeof value === 'string' ? value : value.name)
46
- }
47
- return body
48
- }
@@ -17,12 +17,14 @@ export const assets = createAssetServer({
17
17
  minify: !isDevelopment,
18
18
  watch: isDevelopment,
19
19
  hmr: isHmr
20
- ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
20
+ ? {
21
+ channel: async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel(),
22
+ moduleImporter: 'remix/multiple-import-maps-polyfill',
23
+ }
21
24
  : undefined,
22
25
  scripts: { loaders: isHmr ? [uiHmr()] : undefined },
23
26
  })
24
27
 
25
28
  const entry = 'app/actions/public/entry.ts'
26
29
 
27
- export const entryHref = await assets.getHref(entry)
28
- export const entryPreloads = await assets.getPreloads(entry)
30
+ export const scriptEntry = await assets.getScriptEntry(entry)