rsbuild-plugin-react-router 0.0.4 → 0.1.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/README.md CHANGED
@@ -8,13 +8,17 @@ A Rsbuild plugin that provides seamless integration with React Router, supportin
8
8
 
9
9
  ## Features
10
10
 
11
+
11
12
  - 🚀 Zero-config setup with sensible defaults
12
13
  - 🔄 Automatic route generation from file system
13
14
  - 🖥️ Server-Side Rendering (SSR) support
14
- - 📱 Client-side navigation
15
+ - 📱 Client-side navigation with SPA mode (`ssr: false`)
16
+ - 📄 Static prerendering for hybrid static/dynamic sites
15
17
  - 🛠️ TypeScript support out of the box
16
18
  - 🔧 Customizable configuration
17
19
  - 🎯 Support for route-level code splitting
20
+ - ☁️ Cloudflare Workers deployment support
21
+ - 🔗 Module Federation support (experimental)
18
22
 
19
23
  ## Installation
20
24
 
@@ -26,6 +30,18 @@ yarn add rsbuild-plugin-react-router
26
30
  pnpm add rsbuild-plugin-react-router
27
31
  ```
28
32
 
33
+ ## Local development
34
+
35
+ For the federation examples and Playwright e2e tests, use Node 22 and the
36
+ repo-pinned pnpm version:
37
+
38
+ ```bash
39
+ nvm install
40
+ nvm use
41
+ corepack enable
42
+ corepack prepare pnpm@9.15.3 --activate
43
+ ```
44
+
29
45
  ## Usage
30
46
 
31
47
  Add the plugin to your `rsbuild.config.ts`:
@@ -43,7 +59,7 @@ export default defineConfig(() => {
43
59
  customServer: false,
44
60
  // Optional: Specify server output format
45
61
  serverOutput: "commonjs",
46
- //Optional: enable experimental support for module federation
62
+ // Optional: enable experimental support for module federation
47
63
  federation: false
48
64
  }),
49
65
  pluginReact()
@@ -78,9 +94,18 @@ pluginReactRouter({
78
94
  */
79
95
  federation?: boolean
80
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.
81
106
  ```
82
107
 
83
- 2. **React Router Configuration** (in `react-router.config.ts`):
108
+ 2. **React Router Configuration** (in `react-router.config.*`):
84
109
  ```ts
85
110
  import type { Config } from '@react-router/dev/config';
86
111
 
@@ -91,6 +116,31 @@ export default {
91
116
  */
92
117
  ssr: true,
93
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
+
94
144
  /**
95
145
  * Build directory for output files
96
146
  * @default 'build'
@@ -108,11 +158,110 @@ export default {
108
158
  * @default '/'
109
159
  */
110
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
+ },
111
169
  } satisfies Config;
112
170
  ```
113
171
 
114
172
  All configuration options are optional and will use sensible defaults if not specified.
115
173
 
174
+ ### Config File Resolution
175
+
176
+ The plugin will look for `react-router.config` with any supported JS/TS extension, in this order:
177
+
178
+ - `react-router.config.tsx`
179
+ - `react-router.config.ts`
180
+ - `react-router.config.mts`
181
+ - `react-router.config.jsx`
182
+ - `react-router.config.js`
183
+ - `react-router.config.mjs`
184
+
185
+ If none are found, it falls back to defaults.
186
+
187
+ ### Framework Mode
188
+
189
+ React Router Framework Mode is implemented as a Vite plugin. This Rsbuild
190
+ plugin targets Data Mode only and does not support Framework Mode.
191
+
192
+ ### FAQ
193
+
194
+ #### rsbuild-plugin-react-router vs ModernJS
195
+
196
+ This plugin is a lightweight adapter to run React Router on Rsbuild. It does
197
+ not aim to replace ModernJS or its higher-level framework features. If your
198
+ goal is a full framework or advanced microfrontend support, ModernJS may be
199
+ a better fit.
200
+
201
+ ### SPA Mode (`ssr: false`)
202
+
203
+ React Router's SPA Mode still requires a build-time server render of the root route to generate a hydratable `index.html` (this is how the official React Router Vite plugin works).
204
+
205
+ When `ssr: false`:
206
+
207
+ - The plugin builds both `web` and `node` internally.
208
+ - It generates `build/client/index.html` by running the server build once (requesting `basename` with the `X-React-Router-SPA-Mode: yes` header).
209
+ - It removes `build/server` after generating `index.html`, so the output is deployable as static assets.
210
+
211
+ **Important:** In SPA mode, use `clientLoader` instead of `loader` for data loading since there's no server at runtime.
212
+
213
+ ### Static Prerendering
214
+
215
+ For static sites with multiple pages, you can prerender specific routes at build time:
216
+
217
+ ```ts
218
+ // react-router.config.ts
219
+ import type { Config } from '@react-router/dev/config';
220
+
221
+ export default {
222
+ ssr: false,
223
+ prerender: [
224
+ '/',
225
+ '/about',
226
+ '/docs',
227
+ '/docs/getting-started',
228
+ '/docs/advanced',
229
+ '/projects',
230
+ ],
231
+ } satisfies Config;
232
+ ```
233
+
234
+ When `prerender` is specified:
235
+
236
+ - Each path in the array is rendered at build time
237
+ - Static HTML files are generated for each route (e.g., `/about` → `build/client/about/index.html`)
238
+ - The server build is removed after prerendering for static deployment
239
+ - Non-prerendered routes fall back to client-side routing
240
+
241
+ You can also use `prerender: true` to prerender all static routes automatically.
242
+
243
+ `prerender` can also be a function:
244
+
245
+ ```ts
246
+ export default {
247
+ ssr: false,
248
+ prerender: ({ getStaticPaths }) =>
249
+ getStaticPaths().filter(path => path !== '/admin'),
250
+ } satisfies Config;
251
+ ```
252
+
253
+ For large sites, you can tune prerender concurrency:
254
+
255
+ ```ts
256
+ export default {
257
+ ssr: false,
258
+ prerender: {
259
+ paths: ['/','/about'],
260
+ unstable_concurrency: 4,
261
+ },
262
+ } satisfies Config;
263
+ ```
264
+
116
265
  ### Default Configuration Values
117
266
 
118
267
  If no configuration is provided, the following defaults will be used:
@@ -187,6 +336,7 @@ Route components support the following exports:
187
336
  - `Layout` - Layout component
188
337
  - `clientLoader` - Client-side data loading
189
338
  - `clientAction` - Client-side form actions
339
+ - `clientMiddleware` - Client-side middleware
190
340
  - `handle` - Route handle
191
341
  - `links` - Prefetch links
192
342
  - `meta` - Route meta data
@@ -195,8 +345,24 @@ Route components support the following exports:
195
345
  #### Server-side Exports
196
346
  - `loader` - Server-side data loading
197
347
  - `action` - Server-side form actions
348
+ - `middleware` - Server-side middleware
198
349
  - `headers` - HTTP headers
199
350
 
351
+ ### Client/Server-only Modules
352
+
353
+ - Files ending in `.client.*` are treated as client-only. Their exports are
354
+ stubbed to `undefined` in the server build, so they are safe to import from
355
+ route components for browser-only behavior.
356
+ - Files ending in `.server.*` are server-only. If they are imported by code
357
+ compiled for the web environment, the build will fail with a clear error.
358
+ Keep `.server` imports in server entrypoints or other server-only code.
359
+
360
+ ### Asset Prefix
361
+
362
+ If you configure `output.assetPrefix` in Rsbuild, the plugin uses that value
363
+ for the React Router browser manifest and server build `publicPath` so asset
364
+ URLs resolve correctly when serving from a CDN or sub-path.
365
+
200
366
  ## Custom Server Setup
201
367
 
202
368
  The plugin supports two ways to handle server-side rendering:
@@ -479,6 +645,51 @@ The plugin automatically:
479
645
  - Handles route-based code splitting
480
646
  - Manages client and server builds
481
647
 
648
+ ## React Router Framework Mode
649
+
650
+ React Router "Framework Mode" wraps Data Mode using a Vite plugin. This Rsbuild plugin currently targets React Router's Data Mode build/runtime model and does not implement the Vite plugin layer (type-safe href, route module splitting, etc.).
651
+
652
+ ## Examples
653
+
654
+ The repository includes several examples demonstrating different use cases:
655
+
656
+ | Example | Description | Port | Command |
657
+ |---------|-------------|------|---------|
658
+ | [default-template](./examples/default-template) | Standard SSR setup with React Router | 3000 | `pnpm dev` |
659
+ | [spa-mode](./examples/spa-mode) | Single Page Application (`ssr: false`) | 3001 | `pnpm dev` |
660
+ | [prerender](./examples/prerender) | Static prerendering for multiple routes | 3002 | `pnpm dev` |
661
+ | [custom-node-server](./examples/custom-node-server) | Custom Express server with SSR | 3003 | `pnpm dev` |
662
+ | [cloudflare](./examples/cloudflare) | Cloudflare Workers deployment | 3004 | `pnpm dev` |
663
+ | [client-only](./examples/client-only) | `.client` modules with SSR hydration | 3010 | `pnpm dev` |
664
+ | [epic-stack](./examples/epic-stack) | Full-featured Epic Stack example | 3005 | `pnpm dev` |
665
+ | [federation/epic-stack](./examples/federation/epic-stack) | Module Federation host | 3006 | `pnpm dev` |
666
+ | [federation/epic-stack-remote](./examples/federation/epic-stack-remote) | Module Federation remote | 3007 | `pnpm dev` |
667
+
668
+ Each example has unique ports configured to allow running multiple examples simultaneously.
669
+
670
+ ### Running Examples
671
+
672
+ ```bash
673
+ # Install dependencies
674
+ pnpm install
675
+
676
+ # Build the plugin
677
+ pnpm build
678
+
679
+ # Run any example
680
+ cd examples/default-template
681
+ pnpm dev
682
+ ```
683
+
684
+ ### Running E2E Tests
685
+
686
+ Each example includes Playwright e2e tests:
687
+
688
+ ```bash
689
+ cd examples/default-template
690
+ pnpm test:e2e
691
+ ```
692
+
482
693
  ## License
483
694
 
484
695
  MIT
package/dist/906.js ADDED
@@ -0,0 +1 @@
1
+ export { ServerRouter, createRequestHandler, matchRoutes } from "react-router";
package/dist/946.js ADDED
@@ -0,0 +1 @@
1
+ export { StrictMode, createElement, startTransition } from "react";
package/dist/babel.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { types as Babel } from '@babel/core';
2
2
  import { type ParseResult, parse } from '@babel/parser';
3
3
  import type { NodePath } from '@babel/traverse';
4
4
  import * as t from '@babel/types';
5
- declare const traverse: typeof import("@babel/traverse").default;
6
- declare const generate: typeof import("@babel/generator").default;
5
+ declare const traverse: typeof import('@babel/traverse').default;
6
+ declare const generate: typeof import('@babel/generator').default;
7
7
  export { traverse, generate, parse, t };
8
8
  export type { Babel, NodePath, ParseResult };
@@ -0,0 +1,19 @@
1
+ import type { Config } from './react-router-config.js';
2
+ import type { Route } from './types.js';
3
+ type BuildManifest = {
4
+ routes: Record<string, Route>;
5
+ } | {
6
+ routes: Record<string, Route>;
7
+ serverBundles: Record<string, {
8
+ id: string;
9
+ file: string;
10
+ }>;
11
+ routeIdToServerBundleId: Record<string, string>;
12
+ };
13
+ export declare const getBuildManifest: ({ reactRouterConfig, routes, rootDirectory, }: {
14
+ reactRouterConfig: Required<Pick<Config, "appDirectory" | "buildDirectory" | "serverBuildFile" | "future">> & Pick<Config, "serverBundles">;
15
+ routes: Record<string, Route>;
16
+ rootDirectory: string;
17
+ }) => Promise<BuildManifest | undefined>;
18
+ export declare const getRoutesByServerBundleId: (buildManifest: BuildManifest | undefined) => Record<string, Record<string, Route>>;
19
+ export {};
@@ -1,22 +1,29 @@
1
1
  export declare const PLUGIN_NAME = "rsbuild:react-router";
2
- export declare const JS_EXTENSIONS: readonly [".tsx", ".ts", ".jsx", ".js", ".mjs"];
2
+ export declare const JS_EXTENSIONS: readonly [".tsx", ".ts", ".jsx", ".js", ".mjs", ".mts"];
3
3
  export declare const JS_LOADERS: {
4
4
  readonly '.ts': "ts";
5
5
  readonly '.tsx': "tsx";
6
6
  readonly '.js': "js";
7
7
  readonly '.jsx': "jsx";
8
+ readonly '.mjs': "js";
9
+ readonly '.mts': "ts";
8
10
  };
9
- export declare const SERVER_ONLY_ROUTE_EXPORTS: readonly ["loader", "action", "headers"];
10
- export declare const CLIENT_ROUTE_EXPORTS: readonly ["clientAction", "clientLoader", "default", "ErrorBoundary", "handle", "HydrateFallback", "Layout", "links", "meta", "shouldRevalidate"];
11
+ export declare const BUILD_CLIENT_ROUTE_QUERY_STRING = "?__react-router-build-client-route";
12
+ export declare const SERVER_ONLY_ROUTE_EXPORTS: readonly ["loader", "action", "middleware", "headers"];
13
+ export declare const CLIENT_NON_COMPONENT_EXPORTS: readonly ["clientAction", "clientLoader", "clientMiddleware", "handle", "meta", "links", "shouldRevalidate"];
14
+ export declare const CLIENT_COMPONENT_EXPORTS: readonly ["default", "ErrorBoundary", "HydrateFallback", "Layout"];
15
+ export declare const CLIENT_ROUTE_EXPORTS: readonly ((typeof CLIENT_NON_COMPONENT_EXPORTS)[number] | (typeof CLIENT_COMPONENT_EXPORTS)[number])[];
11
16
  export declare const NAMED_COMPONENT_EXPORTS: readonly ["HydrateFallback", "ErrorBoundary"];
12
17
  export declare const SERVER_EXPORTS: {
13
18
  readonly loader: "loader";
14
19
  readonly action: "action";
20
+ readonly middleware: "middleware";
15
21
  readonly headers: "headers";
16
22
  };
17
23
  export declare const CLIENT_EXPORTS: {
18
24
  readonly clientAction: "clientAction";
19
25
  readonly clientLoader: "clientLoader";
26
+ readonly clientMiddleware: "clientMiddleware";
20
27
  readonly default: "default";
21
28
  readonly ErrorBoundary: "ErrorBoundary";
22
29
  readonly handle: "handle";
@@ -0,0 +1,7 @@
1
+ export declare const transformToEsm: (code: string, resourcePath: string) => Promise<string>;
2
+ export declare const getExportNames: (code: string) => Promise<string[]>;
3
+ export declare const getExportNamesAndExportAll: (code: string) => Promise<{
4
+ exportNames: string[];
5
+ exportAllModules: string[];
6
+ }>;
7
+ export declare const getRouteModuleExports: (resourcePath: string) => Promise<string[]>;