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.
Files changed (105) hide show
  1. package/README.md +213 -201
  2. package/dist/451.js +1103 -0
  3. package/dist/bounded-cache.d.ts +1 -0
  4. package/dist/build-manifest.d.ts +7 -4
  5. package/dist/build-output-transforms.d.ts +30 -0
  6. package/dist/concurrency.d.ts +3 -0
  7. package/dist/config-imports.d.ts +10 -0
  8. package/dist/constants.d.ts +3 -0
  9. package/dist/dev-background-resources.d.ts +38 -0
  10. package/dist/dev-generation.d.ts +25 -0
  11. package/dist/dev-runtime-artifacts.d.ts +47 -0
  12. package/dist/dev-runtime-compilation.d.ts +47 -0
  13. package/dist/dev-runtime-controller.d.ts +14 -0
  14. package/dist/dev-runtime-session.d.ts +34 -0
  15. package/dist/dev-server.d.ts +16 -2
  16. package/dist/effect-runtime.d.ts +18 -0
  17. package/dist/export-utils.d.ts +15 -7
  18. package/dist/index.cjs +13775 -1412
  19. package/dist/index.d.ts +8 -1
  20. package/dist/index.js +9429 -1501
  21. package/dist/lazy-compilation-prewarm.d.ts +25 -0
  22. package/dist/lazy-compilation.d.ts +6 -0
  23. package/dist/manifest.d.ts +48 -10
  24. package/dist/modify-browser-manifest.d.ts +30 -13
  25. package/dist/parallel-route-transform-protocol.d.ts +25 -0
  26. package/dist/parallel-route-transform-worker.d.ts +1 -0
  27. package/dist/parallel-route-transform-worker.js +38 -0
  28. package/dist/parallel-route-transforms.d.ts +29 -0
  29. package/dist/performance.d.ts +26 -0
  30. package/dist/plugin-utils.d.ts +2 -12
  31. package/dist/prerender-build.d.ts +36 -0
  32. package/dist/prerender.d.ts +27 -2
  33. package/dist/react-router-config.d.ts +15 -5
  34. package/dist/route-artifacts.d.ts +33 -0
  35. package/dist/route-ast.d.ts +21 -0
  36. package/dist/route-chunks.d.ts +9 -1
  37. package/dist/route-component-transform.d.ts +5 -0
  38. package/dist/route-export-pruning.d.ts +6 -0
  39. package/dist/route-export-resolution.d.ts +5 -0
  40. package/dist/route-transform-tasks.d.ts +47 -0
  41. package/dist/route-watch.d.ts +27 -0
  42. package/dist/server-build-plan.d.ts +22 -0
  43. package/dist/server-build-resolution.d.ts +3 -0
  44. package/dist/server-utils.d.ts +3 -2
  45. package/dist/ssr-externals.d.ts +1 -0
  46. package/dist/templates/entry.server.cjs +3 -3
  47. package/dist/templates/entry.server.js +3 -3
  48. package/dist/typegen.d.ts +15 -0
  49. package/dist/types.d.ts +41 -14
  50. package/dist/virtual-modules.d.ts +2 -0
  51. package/dist/warnings/warn-on-client-source-maps.d.ts +1 -0
  52. package/dist/yuku.d.ts +15 -0
  53. package/package.json +24 -19
  54. package/src/bounded-cache.ts +18 -0
  55. package/src/build-manifest.ts +205 -0
  56. package/src/build-output-transforms.ts +273 -0
  57. package/src/concurrency.ts +15 -0
  58. package/src/config-imports.ts +83 -0
  59. package/src/constants.ts +91 -0
  60. package/src/dev-background-resources.ts +255 -0
  61. package/src/dev-generation.ts +690 -0
  62. package/src/dev-runtime-artifacts.ts +239 -0
  63. package/src/dev-runtime-compilation.ts +171 -0
  64. package/src/dev-runtime-controller.ts +542 -0
  65. package/src/dev-runtime-session.ts +191 -0
  66. package/src/dev-server.ts +88 -0
  67. package/src/effect-runtime.ts +130 -0
  68. package/src/export-utils.ts +278 -0
  69. package/src/index.ts +990 -0
  70. package/src/lazy-compilation-prewarm.ts +279 -0
  71. package/src/lazy-compilation.ts +101 -0
  72. package/src/manifest.ts +591 -0
  73. package/src/modify-browser-manifest.ts +246 -0
  74. package/src/parallel-route-transform-protocol.ts +35 -0
  75. package/src/parallel-route-transform-worker.ts +82 -0
  76. package/src/parallel-route-transforms.ts +458 -0
  77. package/src/performance.ts +254 -0
  78. package/src/plugin-utils.ts +82 -0
  79. package/src/prerender-build.ts +687 -0
  80. package/src/prerender.ts +349 -0
  81. package/src/react-router-config.ts +245 -0
  82. package/src/route-artifacts.ts +155 -0
  83. package/src/route-ast.ts +163 -0
  84. package/src/route-chunks.ts +857 -0
  85. package/src/route-component-transform.ts +314 -0
  86. package/src/route-config.ts +106 -0
  87. package/src/route-export-pruning.ts +668 -0
  88. package/src/route-export-resolution.ts +329 -0
  89. package/src/route-transform-tasks.ts +249 -0
  90. package/src/route-watch.ts +357 -0
  91. package/src/server-build-plan.ts +91 -0
  92. package/src/server-build-resolution.ts +131 -0
  93. package/src/server-utils.ts +111 -0
  94. package/src/ssr-externals.ts +59 -0
  95. package/src/templates/context.ts +12 -0
  96. package/src/templates/entry.client.tsx +12 -0
  97. package/src/templates/entry.server.tsx +76 -0
  98. package/src/typegen.ts +178 -0
  99. package/src/types.ts +92 -0
  100. package/src/validation/validate-plugin-order.ts +76 -0
  101. package/src/virtual-modules.ts +30 -0
  102. package/src/warnings/warn-on-client-source-maps.ts +96 -0
  103. package/src/yuku.ts +67 -0
  104. package/dist/0~rslib-runtime.js +0 -16
  105. 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
- return {
56
- plugins: [
57
- pluginReactRouter({
58
- // Optional: Enable custom server mode
59
- customServer: false,
60
- // Optional: Specify server output format
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
- The plugin uses a two-part configuration system:
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
- * Whether to disable automatic middleware setup for custom server implementation.
80
- * Enable this when you want to handle server setup manually.
81
- * @default false
82
- */
83
- customServer?: boolean,
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
- 2. **React Router Configuration** (in `react-router.config.*`):
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
- * Base URL path
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
- All configuration options are optional and will use sensible defaults if not specified.
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
- For large sites, you can tune prerender concurrency:
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
- unstable_concurrency: 4,
223
+ paths: ['/', '/about'],
224
+ concurrency: 4,
267
225
  },
268
226
  } satisfies Config;
269
227
  ```
270
228
 
271
- ### Default Configuration Values
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
- If no configuration is provided, the following defaults will be used:
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
- ```ts
276
- // Plugin defaults (rsbuild.config.ts)
277
- {
278
- customServer: false
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
- // Router defaults (react-router.config.ts)
282
- {
283
- ssr: true,
284
- buildDirectory: 'build',
285
- appDirectory: 'app',
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
- pluginReact()
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
- When using a custom server, you'll need to:
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
- 1. Create a server handler (`server/index.ts`):
401
- ```ts
402
- import { createRequestHandler } from '@react-router/express';
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 devServer = await rsbuild.createDevServer();
435
- app.use(devServer.middlewares);
436
-
437
- app.use(async (req, res, next) => {
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
- // Production mode
454
- app.use(express.static(path.join(__dirname, 'build/client'), {
455
- index: false
456
- }));
457
-
458
- // Load the server bundle
459
- const serverBundle = await import('./build/server/static/js/app.js');
460
- // Mount the server app after static file handling
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
- 3. Update your `package.json` scripts:
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: ['workerd', 'worker', 'browser', 'import', 'require'],
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 (`future.v8_splitRouteModules`)
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 | Description | Port | Command |
671
- |---------|-------------|------|---------|
672
- | [default-template](./examples/default-template) | Standard SSR setup with React Router | 3000 | `pnpm dev` |
673
- | [spa-mode](./examples/spa-mode) | Single Page Application (`ssr: false`) | 3001 | `pnpm dev` |
674
- | [prerender](./examples/prerender) | Static prerendering for multiple routes | 3002 | `pnpm dev` |
675
- | [custom-node-server](./examples/custom-node-server) | Custom Express server with SSR | 3003 | `pnpm dev` |
676
- | [cloudflare](./examples/cloudflare) | Cloudflare Workers deployment | 3004 | `pnpm dev` |
677
- | [client-only](./examples/client-only) | `.client` modules with SSR hydration | 3010 | `pnpm dev` |
678
- | [epic-stack](./examples/epic-stack) | Full-featured Epic Stack example | 3005 | `pnpm dev` |
679
- | [federation/epic-stack](./examples/federation/epic-stack) | Module Federation host | 3006 | `pnpm dev` |
680
- | [federation/epic-stack-remote](./examples/federation/epic-stack-remote) | Module Federation remote | 3007 | `pnpm dev` |
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