rsbuild-plugin-react-router 0.2.0 → 0.3.1
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 +213 -201
- package/dist/451.js +1103 -0
- package/dist/bounded-cache.d.ts +1 -0
- package/dist/build-manifest.d.ts +7 -4
- package/dist/build-output-transforms.d.ts +30 -0
- package/dist/concurrency.d.ts +3 -0
- package/dist/config-imports.d.ts +10 -0
- package/dist/constants.d.ts +3 -0
- package/dist/dev-background-resources.d.ts +38 -0
- package/dist/dev-generation.d.ts +25 -0
- package/dist/dev-runtime-artifacts.d.ts +47 -0
- package/dist/dev-runtime-compilation.d.ts +47 -0
- package/dist/dev-runtime-controller.d.ts +14 -0
- package/dist/dev-runtime-session.d.ts +34 -0
- package/dist/dev-server.d.ts +16 -2
- package/dist/effect-runtime.d.ts +18 -0
- package/dist/export-utils.d.ts +15 -7
- package/dist/index.cjs +13775 -1412
- package/dist/index.d.ts +8 -1
- package/dist/index.js +9429 -1501
- package/dist/lazy-compilation-prewarm.d.ts +25 -0
- package/dist/lazy-compilation.d.ts +6 -0
- package/dist/manifest.d.ts +48 -10
- package/dist/modify-browser-manifest.d.ts +30 -13
- package/dist/parallel-route-transform-protocol.d.ts +25 -0
- package/dist/parallel-route-transform-worker.d.ts +1 -0
- package/dist/parallel-route-transform-worker.js +38 -0
- package/dist/parallel-route-transforms.d.ts +29 -0
- package/dist/performance.d.ts +26 -0
- package/dist/plugin-utils.d.ts +2 -12
- package/dist/prerender-build.d.ts +36 -0
- package/dist/prerender.d.ts +27 -2
- package/dist/react-router-config.d.ts +15 -5
- package/dist/route-artifacts.d.ts +33 -0
- package/dist/route-ast.d.ts +21 -0
- package/dist/route-chunks.d.ts +9 -1
- package/dist/route-component-transform.d.ts +5 -0
- package/dist/route-export-pruning.d.ts +6 -0
- package/dist/route-export-resolution.d.ts +5 -0
- package/dist/route-transform-tasks.d.ts +47 -0
- package/dist/route-watch.d.ts +27 -0
- package/dist/server-build-plan.d.ts +22 -0
- package/dist/server-build-resolution.d.ts +3 -0
- package/dist/server-utils.d.ts +3 -2
- package/dist/ssr-externals.d.ts +1 -0
- package/dist/templates/entry.server.cjs +3 -3
- package/dist/templates/entry.server.js +3 -3
- package/dist/typegen.d.ts +15 -0
- package/dist/types.d.ts +41 -14
- package/dist/virtual-modules.d.ts +2 -0
- package/dist/warnings/warn-on-client-source-maps.d.ts +1 -0
- package/dist/yuku.d.ts +15 -0
- package/package.json +24 -19
- package/src/bounded-cache.ts +18 -0
- package/src/build-manifest.ts +205 -0
- package/src/build-output-transforms.ts +273 -0
- package/src/concurrency.ts +15 -0
- package/src/config-imports.ts +83 -0
- package/src/constants.ts +91 -0
- package/src/dev-background-resources.ts +255 -0
- package/src/dev-generation.ts +690 -0
- package/src/dev-runtime-artifacts.ts +239 -0
- package/src/dev-runtime-compilation.ts +171 -0
- package/src/dev-runtime-controller.ts +542 -0
- package/src/dev-runtime-session.ts +191 -0
- package/src/dev-server.ts +88 -0
- package/src/effect-runtime.ts +130 -0
- package/src/export-utils.ts +278 -0
- package/src/index.ts +990 -0
- package/src/lazy-compilation-prewarm.ts +279 -0
- package/src/lazy-compilation.ts +101 -0
- package/src/manifest.ts +591 -0
- package/src/modify-browser-manifest.ts +246 -0
- package/src/parallel-route-transform-protocol.ts +35 -0
- package/src/parallel-route-transform-worker.ts +82 -0
- package/src/parallel-route-transforms.ts +458 -0
- package/src/performance.ts +254 -0
- package/src/plugin-utils.ts +82 -0
- package/src/prerender-build.ts +687 -0
- package/src/prerender.ts +349 -0
- package/src/react-router-config.ts +245 -0
- package/src/route-artifacts.ts +155 -0
- package/src/route-ast.ts +163 -0
- package/src/route-chunks.ts +857 -0
- package/src/route-component-transform.ts +314 -0
- package/src/route-config.ts +106 -0
- package/src/route-export-pruning.ts +668 -0
- package/src/route-export-resolution.ts +329 -0
- package/src/route-transform-tasks.ts +249 -0
- package/src/route-watch.ts +357 -0
- package/src/server-build-plan.ts +91 -0
- package/src/server-build-resolution.ts +131 -0
- package/src/server-utils.ts +111 -0
- package/src/ssr-externals.ts +59 -0
- package/src/templates/context.ts +12 -0
- package/src/templates/entry.client.tsx +12 -0
- package/src/templates/entry.server.tsx +76 -0
- package/src/typegen.ts +178 -0
- package/src/types.ts +92 -0
- package/src/validation/validate-plugin-order.ts +76 -0
- package/src/virtual-modules.ts +30 -0
- package/src/warnings/warn-on-client-source-maps.ts +96 -0
- package/src/yuku.ts +67 -0
- package/dist/0~rslib-runtime.js +0 -16
- package/dist/babel.d.ts +0 -8
package/README.md
CHANGED
|
@@ -8,7 +8,6 @@ A Rsbuild plugin that provides seamless integration with React Router, supportin
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
- 🚀 Zero-config setup with sensible defaults
|
|
13
12
|
- 🔄 Automatic route generation from file system
|
|
14
13
|
- 🖥️ Server-Side Rendering (SSR) support
|
|
@@ -48,128 +47,85 @@ Add the plugin to your `rsbuild.config.ts`:
|
|
|
48
47
|
|
|
49
48
|
```ts
|
|
50
49
|
import { defineConfig } from '@rsbuild/core';
|
|
51
|
-
import { pluginReactRouter } from 'rsbuild-plugin-react-router';
|
|
52
50
|
import { pluginReact } from '@rsbuild/plugin-react';
|
|
51
|
+
import { pluginReactRouter } from 'rsbuild-plugin-react-router';
|
|
53
52
|
|
|
54
|
-
export default defineConfig(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
serverOutput: "commonjs",
|
|
62
|
-
// Optional: enable experimental support for module federation
|
|
63
|
-
federation: false
|
|
64
|
-
}),
|
|
65
|
-
pluginReact()
|
|
66
|
-
],
|
|
67
|
-
};
|
|
53
|
+
export default defineConfig({
|
|
54
|
+
plugins: [
|
|
55
|
+
pluginReactRouter({
|
|
56
|
+
// options here
|
|
57
|
+
}),
|
|
58
|
+
pluginReact(),
|
|
59
|
+
],
|
|
68
60
|
});
|
|
69
61
|
```
|
|
70
62
|
|
|
71
63
|
## Configuration
|
|
72
64
|
|
|
73
|
-
|
|
65
|
+
React Router application settings live in `react-router.config.*`. The Rsbuild
|
|
66
|
+
plugin only needs options for Rsbuild-specific behavior.
|
|
67
|
+
|
|
68
|
+
### Plugin Options
|
|
74
69
|
|
|
75
|
-
1. **Plugin Options** (in `rsbuild.config.ts`):
|
|
76
70
|
```ts
|
|
77
71
|
pluginReactRouter({
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Specify the output format for server-side code.
|
|
87
|
-
* Options: "commonjs" | "module"
|
|
88
|
-
* @default "module"
|
|
89
|
-
*/
|
|
90
|
-
serverOutput?: "commonjs" | "module"
|
|
91
|
-
/**
|
|
92
|
-
* Enable experimental support for module federation
|
|
93
|
-
* @default false
|
|
94
|
-
*/
|
|
95
|
-
federation?: boolean
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
When Module Federation is enabled, configure your Federation plugin with
|
|
99
|
-
`experiments.asyncStartup: true` to avoid requiring entrypoint `import()` hacks.
|
|
100
|
-
See the Module Federation examples under `examples/federation`.
|
|
101
|
-
|
|
102
|
-
When Module Federation is enabled, some runtimes may expose server build exports
|
|
103
|
-
as async getters. The dev server resolves these exports automatically. For
|
|
104
|
-
production, use a custom server or an adapter that resolves async exports before
|
|
105
|
-
passing the build to React Router's request handler.
|
|
72
|
+
customServer: false,
|
|
73
|
+
lazyCompilation: true,
|
|
74
|
+
unstableLazyCompilationPrewarm: false,
|
|
75
|
+
logPerformance: false,
|
|
76
|
+
federation: false,
|
|
77
|
+
});
|
|
106
78
|
```
|
|
107
79
|
|
|
108
|
-
|
|
80
|
+
| Option | Default | Description |
|
|
81
|
+
| -------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `customServer` | `false` | Disables the built-in development SSR middleware. Enable this when an app owns the server with `createDevServer()` or an adapter. |
|
|
83
|
+
| `serverOutput` | Derived | Emitted Rsbuild server format: `'module'` or `'commonjs'`. When omitted, React Router's `serverModuleFormat` selects the format (`'esm'` -> `'module'`, `'cjs'` -> `'commonjs'`); setting `serverOutput` overrides it. |
|
|
84
|
+
| `lazyCompilation` | `true` | Optional Rsbuild dev lazy-compilation config. When enabled here or through `dev.lazyCompilation`, React Router hydration-critical modules stay eager so the browser manifest and route modules are not replaced by lazy proxies. |
|
|
85
|
+
| `unstableLazyCompilationPrewarm` | `false` | Experimental prewarm for emitted Rspack lazy-compilation proxy modules after dev compiles. Enable with `true` when route JS proxy startup should happen shortly after compiler readiness. |
|
|
86
|
+
| `logPerformance` | `false` | Logs structured React Router plugin timing information through the Rsbuild logger. |
|
|
87
|
+
| `parallelRouteTransform` | `undefined` | Controls worker-thread route transforms. `undefined` auto-enables workers for 256+ routes, `true` forces the default worker count (in dev this is 0 on machines with 4 or fewer cores, where workers cost more than they save; production builds always use workers), a positive integer sets the worker count, and `false` keeps transforms inline. |
|
|
88
|
+
| `onRouteTopologyChange` | `undefined` | Notification for programmatic/custom dev servers. Recreate the Rsbuild server when route files are added, removed, or moved. The callback is not awaited. |
|
|
89
|
+
| `federation` | `false` | Enables the plugin's experimental Module Federation integration. |
|
|
90
|
+
|
|
91
|
+
When `federation` is enabled, configure the Module Federation plugin with
|
|
92
|
+
`experiments.asyncStartup: true`. The dev server resolves async server build
|
|
93
|
+
exports automatically; production custom servers or adapters should resolve
|
|
94
|
+
async exports before passing the build to React Router's request handler.
|
|
95
|
+
|
|
96
|
+
### React Router Config
|
|
97
|
+
|
|
98
|
+
Put React Router framework settings in `react-router.config.*`:
|
|
99
|
+
|
|
109
100
|
```ts
|
|
110
101
|
import type { Config } from '@react-router/dev/config';
|
|
111
102
|
|
|
112
103
|
export default {
|
|
113
|
-
/**
|
|
114
|
-
* Whether to enable Server-Side Rendering (SSR) support.
|
|
115
|
-
* @default true
|
|
116
|
-
*/
|
|
117
104
|
ssr: true,
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* The file name for the server build output.
|
|
121
|
-
* @default "index.js"
|
|
122
|
-
*/
|
|
123
|
-
serverBuildFile: "index.js",
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* The output format for the server build.
|
|
127
|
-
* Options: "esm" | "cjs"
|
|
128
|
-
* @default "esm"
|
|
129
|
-
*/
|
|
130
|
-
serverModuleFormat: "esm",
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Split server bundles by route branch (advanced).
|
|
134
|
-
*/
|
|
135
|
-
serverBundles: async ({ branch }) => branch[0]?.id ?? "main",
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Hook called after the build completes.
|
|
139
|
-
*/
|
|
140
|
-
buildEnd: async ({ buildManifest, reactRouterConfig }) => {
|
|
141
|
-
console.log(buildManifest, reactRouterConfig);
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Build directory for output files
|
|
146
|
-
* @default 'build'
|
|
147
|
-
*/
|
|
148
|
-
buildDirectory: 'dist',
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Application source directory
|
|
152
|
-
* @default 'app'
|
|
153
|
-
*/
|
|
105
|
+
buildDirectory: 'build',
|
|
154
106
|
appDirectory: 'app',
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
* @default '/'
|
|
159
|
-
*/
|
|
160
|
-
basename: '/my-app',
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* React Router future flags (optional).
|
|
164
|
-
* Example: split client route modules into separate chunks.
|
|
165
|
-
*/
|
|
166
|
-
future: {
|
|
167
|
-
v8_splitRouteModules: true,
|
|
168
|
-
},
|
|
107
|
+
basename: '/',
|
|
108
|
+
splitRouteModules: true,
|
|
109
|
+
subResourceIntegrity: false,
|
|
169
110
|
} satisfies Config;
|
|
170
111
|
```
|
|
171
112
|
|
|
172
|
-
|
|
113
|
+
Commonly used options:
|
|
114
|
+
|
|
115
|
+
| Option | Default | Notes |
|
|
116
|
+
| ---------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| `ssr` | `true` | Set `false` for SPA mode. SPA mode still runs a build-time server render to create `build/client/index.html`. |
|
|
118
|
+
| `buildDirectory` | `'build'` | Output root. Client assets go in `<buildDirectory>/client`; server output goes in `<buildDirectory>/server`. |
|
|
119
|
+
| `appDirectory` | `'app'` | Directory containing `root`, `routes`, and optional `entry.client` / `entry.server` files. |
|
|
120
|
+
| `basename` | `'/'` | Base URL used for routing, prerender requests, and manifest asset paths. |
|
|
121
|
+
| `serverBuildFile` | `'index.js'` | Server build file name. It must end in `.js`. |
|
|
122
|
+
| `serverModuleFormat` | `'esm'` | React Router server module format: `'esm'` or `'cjs'`. `serverOutput` can override the emitted Rsbuild server format. |
|
|
123
|
+
| `serverBundles` | `undefined` | Advanced server bundle splitting by route branch. Disabled when `ssr: false`. |
|
|
124
|
+
| `routeDiscovery` | React Router | Defaults to lazy discovery for SSR and initial discovery for SPA mode. `routeDiscovery.mode: 'lazy'` is invalid for SPA. |
|
|
125
|
+
| `prerender` | `undefined` | `true`, an array of paths, a function, or `{ paths, concurrency }` / `{ paths, unstable_concurrency }`. |
|
|
126
|
+
| `splitRouteModules` | `true` | Splits client route module exports. The legacy `future.v8_splitRouteModules` flag is also accepted. |
|
|
127
|
+
| `subResourceIntegrity` | `false` | Emits SRI metadata for browser scripts. The legacy `future.unstable_subResourceIntegrity` flag is normalized to this key. |
|
|
128
|
+
| `buildEnd` | `undefined` | Hook called after the build with the React Router build manifest and resolved config. |
|
|
173
129
|
|
|
174
130
|
### Config File Resolution
|
|
175
131
|
|
|
@@ -256,36 +212,41 @@ export default {
|
|
|
256
212
|
} satisfies Config;
|
|
257
213
|
```
|
|
258
214
|
|
|
259
|
-
|
|
215
|
+
Prerendering defaults to one path at a time, matching React Router. Use
|
|
216
|
+
`concurrency` for larger sites; `unstable_concurrency` is still accepted for
|
|
217
|
+
older configs:
|
|
260
218
|
|
|
261
219
|
```ts
|
|
262
220
|
export default {
|
|
263
221
|
ssr: false,
|
|
264
222
|
prerender: {
|
|
265
|
-
paths: ['/','/about'],
|
|
266
|
-
|
|
223
|
+
paths: ['/', '/about'],
|
|
224
|
+
concurrency: 4,
|
|
267
225
|
},
|
|
268
226
|
} satisfies Config;
|
|
269
227
|
```
|
|
270
228
|
|
|
271
|
-
|
|
229
|
+
For builds with 256+ routes, detailed file-size reporting is compacted to totals
|
|
230
|
+
by default to avoid gzipping and printing thousands of assets. Set
|
|
231
|
+
`performance.printFileSize` to an object to customize that output.
|
|
272
232
|
|
|
273
|
-
|
|
233
|
+
Route transform source maps are generated in development only. If you enable
|
|
234
|
+
Rsbuild source maps for faster local debugging, prefer a cheap JS map:
|
|
235
|
+
`output.sourceMap: { js: 'cheap-module-source-map', css: false }`.
|
|
274
236
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
237
|
+
Lazy compilation prewarming is disabled by default. When enabled alongside
|
|
238
|
+
`lazyCompilation`, the plugin fetches emitted browser entry and route JS assets,
|
|
239
|
+
extracts activation keys from Rspack's generated lazy-compilation client calls,
|
|
240
|
+
and POSTs those keys to Rspack's configured lazy trigger endpoint after dev
|
|
241
|
+
compiles. It does not request application routes or run route loaders. Because
|
|
242
|
+
the key extraction depends on Rspack's generated client code shape, opt in with
|
|
243
|
+
`unstableLazyCompilationPrewarm: true`.
|
|
280
244
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
basename: '/'
|
|
287
|
-
}
|
|
288
|
-
```
|
|
245
|
+
Subresource Integrity is disabled by default. Enable it with
|
|
246
|
+
`subResourceIntegrity: true` in `react-router.config.*` when the deployed app
|
|
247
|
+
should emit integrity metadata for browser scripts. The legacy
|
|
248
|
+
`future.unstable_subResourceIntegrity` flag is still accepted and is normalized
|
|
249
|
+
to the stable option.
|
|
289
250
|
|
|
290
251
|
### Route Configuration
|
|
291
252
|
|
|
@@ -326,6 +287,7 @@ export default [
|
|
|
326
287
|
```
|
|
327
288
|
|
|
328
289
|
The plugin provides several helper functions for defining routes:
|
|
290
|
+
|
|
329
291
|
- `index()` - Creates an index route
|
|
330
292
|
- `route()` - Creates a regular route with a path
|
|
331
293
|
- `layout()` - Creates a layout route with nested children
|
|
@@ -336,6 +298,7 @@ The plugin provides several helper functions for defining routes:
|
|
|
336
298
|
Route components support the following exports:
|
|
337
299
|
|
|
338
300
|
#### Client-side Exports
|
|
301
|
+
|
|
339
302
|
- `default` - The route component
|
|
340
303
|
- `ErrorBoundary` - Error boundary component
|
|
341
304
|
- `HydrateFallback` - Loading component during hydration
|
|
@@ -349,6 +312,7 @@ Route components support the following exports:
|
|
|
349
312
|
- `shouldRevalidate` - Revalidation control
|
|
350
313
|
|
|
351
314
|
#### Server-side Exports
|
|
315
|
+
|
|
352
316
|
- `loader` - Server-side data loading
|
|
353
317
|
- `action` - Server-side form actions
|
|
354
318
|
- `middleware` - Server-side middleware
|
|
@@ -387,34 +351,39 @@ export default defineConfig(() => {
|
|
|
387
351
|
return {
|
|
388
352
|
plugins: [
|
|
389
353
|
pluginReactRouter({
|
|
390
|
-
customServer: true
|
|
391
|
-
|
|
392
|
-
|
|
354
|
+
customServer: true,
|
|
355
|
+
onRouteTopologyChange() {
|
|
356
|
+
console.warn('Route topology changed; restart the dev server.');
|
|
357
|
+
process.exitCode = 75;
|
|
358
|
+
setTimeout(() => process.exit(75), 0);
|
|
359
|
+
},
|
|
360
|
+
}),
|
|
361
|
+
pluginReact(),
|
|
393
362
|
],
|
|
394
363
|
};
|
|
395
364
|
});
|
|
396
365
|
```
|
|
397
366
|
|
|
398
|
-
|
|
367
|
+
Rsbuild's `reload-server` watcher is owned by the CLI and is not installed by
|
|
368
|
+
the programmatic `createDevServer()` API. The sample below therefore treats
|
|
369
|
+
route topology changes as a full process restart: do not call `startServer()`
|
|
370
|
+
again inside the same process or mount a second dev server on the same Express
|
|
371
|
+
app. If you implement in-process replacement instead, route requests through
|
|
372
|
+
replaceable middleware and request-handler delegates, always `await` the active
|
|
373
|
+
server's `close()` before calling `createDevServer()` again, and do not launch
|
|
374
|
+
concurrent replacements.
|
|
399
375
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
export const app = createRequestHandler({
|
|
405
|
-
build: () => import('virtual/react-router/server-build'),
|
|
406
|
-
getLoadContext() {
|
|
407
|
-
// Add custom context available to your loaders/actions
|
|
408
|
-
return {
|
|
409
|
-
// ... your custom context
|
|
410
|
-
};
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
```
|
|
376
|
+
Create one server entry point (`server.js`) and let it own the React Router
|
|
377
|
+
request handler in both development and production. Only the build provider
|
|
378
|
+
changes between modes:
|
|
414
379
|
|
|
415
|
-
2. Set up your server entry point (`server.js`):
|
|
416
380
|
```js
|
|
417
381
|
import { createRsbuild, loadConfig } from '@rsbuild/core';
|
|
382
|
+
import { createRequestHandler } from '@react-router/express';
|
|
383
|
+
import {
|
|
384
|
+
loadReactRouterServerBuild,
|
|
385
|
+
resolveReactRouterServerBuild,
|
|
386
|
+
} from 'rsbuild-plugin-react-router';
|
|
418
387
|
import express from 'express';
|
|
419
388
|
import path from 'path';
|
|
420
389
|
import { fileURLToPath } from 'url';
|
|
@@ -426,80 +395,95 @@ const app = express();
|
|
|
426
395
|
const isDev = process.env.NODE_ENV !== 'production';
|
|
427
396
|
|
|
428
397
|
async function startServer() {
|
|
398
|
+
let devServer;
|
|
399
|
+
let build;
|
|
400
|
+
|
|
429
401
|
if (isDev) {
|
|
430
402
|
const config = await loadConfig();
|
|
431
403
|
const rsbuild = await createRsbuild({
|
|
432
404
|
rsbuildConfig: config.content,
|
|
433
405
|
});
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
try {
|
|
439
|
-
const bundle = await devServer.environments.node.loadBundle('app');
|
|
440
|
-
await bundle.app(req, res, next);
|
|
441
|
-
} catch (e) {
|
|
442
|
-
next(e);
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
const port = Number.parseInt(process.env.PORT || '3000', 10);
|
|
447
|
-
const server = app.listen(port, () => {
|
|
448
|
-
console.log(`Development server is running on http://localhost:${port}`);
|
|
449
|
-
devServer.afterListen();
|
|
450
|
-
});
|
|
451
|
-
devServer.connectWebSocket({ server });
|
|
406
|
+
const currentDevServer = await rsbuild.createDevServer();
|
|
407
|
+
devServer = currentDevServer;
|
|
408
|
+
app.use(currentDevServer.middlewares);
|
|
409
|
+
build = () => loadReactRouterServerBuild(currentDevServer);
|
|
452
410
|
} else {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
app.use(async (req, res, next) => {
|
|
462
|
-
try {
|
|
463
|
-
await serverBundle.default.app(req, res, next);
|
|
464
|
-
} catch (e) {
|
|
465
|
-
next(e);
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
const port = Number.parseInt(process.env.PORT || '3000', 10);
|
|
470
|
-
app.listen(port, () => {
|
|
471
|
-
console.log(`Production server is running on http://localhost:${port}`);
|
|
472
|
-
});
|
|
411
|
+
app.use(
|
|
412
|
+
express.static(path.join(__dirname, 'build/client'), {
|
|
413
|
+
index: false,
|
|
414
|
+
})
|
|
415
|
+
);
|
|
416
|
+
build = await resolveReactRouterServerBuild(
|
|
417
|
+
import('./build/server/static/js/app.js')
|
|
418
|
+
);
|
|
473
419
|
}
|
|
420
|
+
|
|
421
|
+
app.use(
|
|
422
|
+
createRequestHandler({
|
|
423
|
+
build,
|
|
424
|
+
mode: isDev ? 'development' : 'production',
|
|
425
|
+
getLoadContext() {
|
|
426
|
+
return {
|
|
427
|
+
// Add custom loader/action context here.
|
|
428
|
+
};
|
|
429
|
+
},
|
|
430
|
+
})
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const port = Number.parseInt(process.env.PORT || '3000', 10);
|
|
434
|
+
const server = app.listen(port, () => {
|
|
435
|
+
const mode = isDev ? 'Development' : 'Production';
|
|
436
|
+
console.log(`${mode} server is running on http://localhost:${port}`);
|
|
437
|
+
devServer?.afterListen();
|
|
438
|
+
});
|
|
439
|
+
devServer?.connectWebSocket({ server });
|
|
474
440
|
}
|
|
475
441
|
|
|
476
442
|
startServer().catch(console.error);
|
|
477
443
|
```
|
|
478
444
|
|
|
479
|
-
|
|
445
|
+
`loadReactRouterServerBuild` waits for a complete React Router development
|
|
446
|
+
generation. During rebuilds it returns the last successfully evaluated server
|
|
447
|
+
build, whose embedded manifest is paired with the selected web compilation.
|
|
448
|
+
A failed or incomplete candidate does not replace that last-good pair. The
|
|
449
|
+
built-in development middleware uses the same path. Calling
|
|
450
|
+
`devServer.environments.node.loadBundle()` directly bypasses this guarantee.
|
|
451
|
+
|
|
452
|
+
When `serverBundles` is configured, pass its exact Rsbuild entry name as the
|
|
453
|
+
optional second argument (for example, `bundle-a/index`). The default build
|
|
454
|
+
and every configured bundle are
|
|
455
|
+
evaluated and published as one generation; one failing bundle keeps the whole
|
|
456
|
+
previous generation active.
|
|
457
|
+
|
|
458
|
+
`resolveReactRouterServerBuild` accepts an imported production server module,
|
|
459
|
+
normalizes ESM and CommonJS namespace shapes, resolves supported asynchronous
|
|
460
|
+
build exports, and validates the result before it reaches React Router.
|
|
461
|
+
|
|
462
|
+
This guarantee covers the eagerly evaluated server entry object and its
|
|
463
|
+
embedded manifest. It does not snapshot deferred server chunks, make emitted
|
|
464
|
+
client assets immutable, or delay Rsbuild's WebSocket success notification.
|
|
465
|
+
Same-path server or client chunks can change before the matching framework
|
|
466
|
+
generation commits. Closing that publication gap requires a supported Rsbuild
|
|
467
|
+
graph-settled hook plus immutable or staged outputs.
|
|
468
|
+
|
|
469
|
+
Then update your `package.json` scripts:
|
|
470
|
+
|
|
480
471
|
```json
|
|
481
472
|
{
|
|
482
473
|
"scripts": {
|
|
483
|
-
"dev": "node server.js",
|
|
474
|
+
"dev": "NODE_ENV=development NODE_OPTIONS=\"--experimental-vm-modules\" node server.js",
|
|
484
475
|
"build": "rsbuild build",
|
|
485
476
|
"start": "NODE_ENV=production node server.js"
|
|
486
477
|
}
|
|
487
478
|
}
|
|
488
479
|
```
|
|
489
480
|
|
|
490
|
-
The custom server setup allows you to:
|
|
491
|
-
- Add custom middleware
|
|
492
|
-
- Handle API routes
|
|
493
|
-
- Integrate with databases
|
|
494
|
-
- Implement custom authentication
|
|
495
|
-
- Add server-side caching
|
|
496
|
-
- And more!
|
|
497
|
-
|
|
498
481
|
## Cloudflare Workers Deployment
|
|
499
482
|
|
|
500
483
|
To deploy your React Router app to Cloudflare Workers:
|
|
501
484
|
|
|
502
485
|
1. **Configure Rsbuild** (`rsbuild.config.ts`):
|
|
486
|
+
|
|
503
487
|
```ts
|
|
504
488
|
import { defineConfig } from '@rsbuild/core';
|
|
505
489
|
import { pluginReact } from '@rsbuild/plugin-react';
|
|
@@ -524,17 +508,24 @@ export default defineConfig({
|
|
|
524
508
|
module: true,
|
|
525
509
|
},
|
|
526
510
|
resolve: {
|
|
527
|
-
conditionNames: [
|
|
511
|
+
conditionNames: [
|
|
512
|
+
'workerd',
|
|
513
|
+
'worker',
|
|
514
|
+
'browser',
|
|
515
|
+
'import',
|
|
516
|
+
'require',
|
|
517
|
+
],
|
|
528
518
|
},
|
|
529
519
|
},
|
|
530
520
|
},
|
|
531
521
|
},
|
|
532
522
|
},
|
|
533
|
-
plugins: [pluginReactRouter({customServer: true}), pluginReact()],
|
|
523
|
+
plugins: [pluginReactRouter({ customServer: true }), pluginReact()],
|
|
534
524
|
});
|
|
535
525
|
```
|
|
536
526
|
|
|
537
527
|
2. **Configure Wrangler** (`wrangler.toml`):
|
|
528
|
+
|
|
538
529
|
```toml
|
|
539
530
|
workers_dev = true
|
|
540
531
|
name = "my-react-router-worker"
|
|
@@ -552,6 +543,7 @@ VALUE_FROM_CLOUDFLARE = "Hello from Cloudflare"
|
|
|
552
543
|
```
|
|
553
544
|
|
|
554
545
|
3. **Create Worker Entry** (`server/index.ts`):
|
|
546
|
+
|
|
555
547
|
```ts
|
|
556
548
|
import { createRequestHandler } from 'react-router';
|
|
557
549
|
|
|
@@ -588,6 +580,7 @@ export default {
|
|
|
588
580
|
```
|
|
589
581
|
|
|
590
582
|
4. **Update Package Dependencies**:
|
|
583
|
+
|
|
591
584
|
```json
|
|
592
585
|
{
|
|
593
586
|
"dependencies": {
|
|
@@ -605,6 +598,7 @@ export default {
|
|
|
605
598
|
```
|
|
606
599
|
|
|
607
600
|
5. **Setup Deployment Scripts** (`package.json`):
|
|
601
|
+
|
|
608
602
|
```json
|
|
609
603
|
{
|
|
610
604
|
"scripts": {
|
|
@@ -630,6 +624,7 @@ export default {
|
|
|
630
624
|
### Development Workflow:
|
|
631
625
|
|
|
632
626
|
1. Local Development:
|
|
627
|
+
|
|
633
628
|
```bash
|
|
634
629
|
# Start local development server
|
|
635
630
|
npm run dev
|
|
@@ -646,18 +641,35 @@ export default {
|
|
|
646
641
|
## Development
|
|
647
642
|
|
|
648
643
|
The plugin automatically:
|
|
644
|
+
|
|
649
645
|
- Runs type generation during development and build
|
|
650
646
|
- Sets up development server with live reload
|
|
651
647
|
- Handles route-based code splitting
|
|
652
648
|
- Manages client and server builds
|
|
653
649
|
|
|
650
|
+
### Benchmarking
|
|
651
|
+
|
|
652
|
+
`pnpm bench:large` runs this repository's generated stress fixture for quick
|
|
653
|
+
regression checks. `pnpm bench:synthetic-app` runs the embedded complex Rsbuild
|
|
654
|
+
app under `benchmarks/synthetic-web-bundler-benchmark`, which adds heavier
|
|
655
|
+
loader and transform contention for benchmark coverage closer to a large
|
|
656
|
+
real-world application.
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
pnpm bench:large
|
|
660
|
+
pnpm bench:synthetic-app -- --profile all --runs 2
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
The PR benchmark workflow reports production build, dev route-load, HMR/update,
|
|
664
|
+
and embedded synthetic app timings in the same benchmark comment.
|
|
665
|
+
|
|
654
666
|
## React Router Framework Mode
|
|
655
667
|
|
|
656
668
|
React Router "Framework Mode" wraps Data Mode using a Vite plugin. This Rsbuild
|
|
657
669
|
plugin aims to match the important behaviors without depending on Vite:
|
|
658
670
|
|
|
659
671
|
- Typegen + Route Module API types (`./+types/*`)
|
|
660
|
-
- Route module splitting (`
|
|
672
|
+
- Route module splitting (`splitRouteModules`)
|
|
661
673
|
- SPA mode (`ssr: false`), SSR mode, and static prerendering (`prerender`)
|
|
662
674
|
|
|
663
675
|
Some Vite-specific integrations (for example Vite's environment API + critical
|
|
@@ -667,17 +679,17 @@ CSS endpoint) are not supported 1:1.
|
|
|
667
679
|
|
|
668
680
|
The repository includes several examples demonstrating different use cases:
|
|
669
681
|
|
|
670
|
-
| Example
|
|
671
|
-
|
|
672
|
-
| [default-template](./examples/default-template)
|
|
673
|
-
| [spa-mode](./examples/spa-mode)
|
|
674
|
-
| [prerender](./examples/prerender)
|
|
675
|
-
| [custom-node-server](./examples/custom-node-server)
|
|
676
|
-
| [cloudflare](./examples/cloudflare)
|
|
677
|
-
| [client-only](./examples/client-only)
|
|
678
|
-
| [epic-stack](./examples/epic-stack)
|
|
679
|
-
| [federation/epic-stack](./examples/federation/epic-stack)
|
|
680
|
-
| [federation/epic-stack-remote](./examples/federation/epic-stack-remote) | Module Federation remote
|
|
682
|
+
| Example | Description | Port | Command |
|
|
683
|
+
| ----------------------------------------------------------------------- | --------------------------------------- | ---- | ---------- |
|
|
684
|
+
| [default-template](./examples/default-template) | Standard SSR setup with React Router | 3000 | `pnpm dev` |
|
|
685
|
+
| [spa-mode](./examples/spa-mode) | Single Page Application (`ssr: false`) | 3001 | `pnpm dev` |
|
|
686
|
+
| [prerender](./examples/prerender) | Static prerendering for multiple routes | 3002 | `pnpm dev` |
|
|
687
|
+
| [custom-node-server](./examples/custom-node-server) | Custom Express server with SSR | 3003 | `pnpm dev` |
|
|
688
|
+
| [cloudflare](./examples/cloudflare) | Cloudflare Workers deployment | 3004 | `pnpm dev` |
|
|
689
|
+
| [client-only](./examples/client-only) | `.client` modules with SSR hydration | 3010 | `pnpm dev` |
|
|
690
|
+
| [epic-stack](./examples/epic-stack) | Full-featured Epic Stack example | 3005 | `pnpm dev` |
|
|
691
|
+
| [federation/epic-stack](./examples/federation/epic-stack) | Module Federation host | 3006 | `pnpm dev` |
|
|
692
|
+
| [federation/epic-stack-remote](./examples/federation/epic-stack-remote) | Module Federation remote | 3007 | `pnpm dev` |
|
|
681
693
|
|
|
682
694
|
Each example has unique ports configured to allow running multiple examples simultaneously.
|
|
683
695
|
|