rsbuild-plugin-react-router 0.0.5 → 0.1.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 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,116 @@ 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, but this Rsbuild
190
+ plugin aims to provide equivalent **framework-mode behaviors** (typegen, Route
191
+ Module API types, route module splitting, SPA/SSR/prerender strategies) on top
192
+ of Rsbuild/Rspack.
193
+
194
+ In practice, you should be able to use the `@react-router/dev/*` config + routes
195
+ APIs, import generated `./+types/*` in route modules, and use the standard
196
+ `entry.client`/`entry.server` entrypoints like you would in the official setup.
197
+
198
+ ### FAQ
199
+
200
+ #### rsbuild-plugin-react-router vs ModernJS
201
+
202
+ This plugin is a lightweight adapter to run React Router on Rsbuild. It does
203
+ not aim to replace ModernJS or its higher-level framework features. If your
204
+ goal is a full framework or advanced microfrontend support, ModernJS may be
205
+ a better fit.
206
+
207
+ ### SPA Mode (`ssr: false`)
208
+
209
+ 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).
210
+
211
+ When `ssr: false`:
212
+
213
+ - The plugin builds both `web` and `node` internally.
214
+ - It generates `build/client/index.html` by running the server build once (requesting `basename` with the `X-React-Router-SPA-Mode: yes` header).
215
+ - It removes `build/server` after generating `index.html`, so the output is deployable as static assets.
216
+
217
+ **Important:** In SPA mode, use `clientLoader` instead of `loader` for data loading since there's no server at runtime.
218
+
219
+ ### Static Prerendering
220
+
221
+ For static sites with multiple pages, you can prerender specific routes at build time:
222
+
223
+ ```ts
224
+ // react-router.config.ts
225
+ import type { Config } from '@react-router/dev/config';
226
+
227
+ export default {
228
+ ssr: false,
229
+ prerender: [
230
+ '/',
231
+ '/about',
232
+ '/docs',
233
+ '/docs/getting-started',
234
+ '/docs/advanced',
235
+ '/projects',
236
+ ],
237
+ } satisfies Config;
238
+ ```
239
+
240
+ When `prerender` is specified:
241
+
242
+ - Each path in the array is rendered at build time
243
+ - Static HTML files are generated for each route (e.g., `/about` → `build/client/about/index.html`)
244
+ - The server build is removed after prerendering for static deployment
245
+ - Non-prerendered routes fall back to client-side routing
246
+
247
+ You can also use `prerender: true` to prerender all static routes automatically.
248
+
249
+ `prerender` can also be a function:
250
+
251
+ ```ts
252
+ export default {
253
+ ssr: false,
254
+ prerender: ({ getStaticPaths }) =>
255
+ getStaticPaths().filter(path => path !== '/admin'),
256
+ } satisfies Config;
257
+ ```
258
+
259
+ For large sites, you can tune prerender concurrency:
260
+
261
+ ```ts
262
+ export default {
263
+ ssr: false,
264
+ prerender: {
265
+ paths: ['/','/about'],
266
+ unstable_concurrency: 4,
267
+ },
268
+ } satisfies Config;
269
+ ```
270
+
116
271
  ### Default Configuration Values
117
272
 
118
273
  If no configuration is provided, the following defaults will be used:
@@ -187,6 +342,7 @@ Route components support the following exports:
187
342
  - `Layout` - Layout component
188
343
  - `clientLoader` - Client-side data loading
189
344
  - `clientAction` - Client-side form actions
345
+ - `clientMiddleware` - Client-side middleware
190
346
  - `handle` - Route handle
191
347
  - `links` - Prefetch links
192
348
  - `meta` - Route meta data
@@ -195,8 +351,24 @@ Route components support the following exports:
195
351
  #### Server-side Exports
196
352
  - `loader` - Server-side data loading
197
353
  - `action` - Server-side form actions
354
+ - `middleware` - Server-side middleware
198
355
  - `headers` - HTTP headers
199
356
 
357
+ ### Client/Server-only Modules
358
+
359
+ - Files ending in `.client.*` are treated as client-only. Their exports are
360
+ stubbed to `undefined` in the server build, so they are safe to import from
361
+ route components for browser-only behavior.
362
+ - Files ending in `.server.*` are server-only. If they are imported by code
363
+ compiled for the web environment, the build will fail with a clear error.
364
+ Keep `.server` imports in server entrypoints or other server-only code.
365
+
366
+ ### Asset Prefix
367
+
368
+ If you configure `output.assetPrefix` in Rsbuild, the plugin uses that value
369
+ for the React Router browser manifest and server build `publicPath` so asset
370
+ URLs resolve correctly when serving from a CDN or sub-path.
371
+
200
372
  ## Custom Server Setup
201
373
 
202
374
  The plugin supports two ways to handle server-side rendering:
@@ -479,6 +651,59 @@ The plugin automatically:
479
651
  - Handles route-based code splitting
480
652
  - Manages client and server builds
481
653
 
654
+ ## React Router Framework Mode
655
+
656
+ React Router "Framework Mode" wraps Data Mode using a Vite plugin. This Rsbuild
657
+ plugin aims to match the important behaviors without depending on Vite:
658
+
659
+ - Typegen + Route Module API types (`./+types/*`)
660
+ - Route module splitting (`future.v8_splitRouteModules`)
661
+ - SPA mode (`ssr: false`), SSR mode, and static prerendering (`prerender`)
662
+
663
+ Some Vite-specific integrations (for example Vite's environment API + critical
664
+ CSS endpoint) are not supported 1:1.
665
+
666
+ ## Examples
667
+
668
+ The repository includes several examples demonstrating different use cases:
669
+
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` |
681
+
682
+ Each example has unique ports configured to allow running multiple examples simultaneously.
683
+
684
+ ### Running Examples
685
+
686
+ ```bash
687
+ # Install dependencies
688
+ pnpm install
689
+
690
+ # Build the plugin
691
+ pnpm build
692
+
693
+ # Run any example
694
+ cd examples/default-template
695
+ pnpm dev
696
+ ```
697
+
698
+ ### Running E2E Tests
699
+
700
+ Each example includes Playwright e2e tests:
701
+
702
+ ```bash
703
+ cd examples/default-template
704
+ pnpm test:e2e
705
+ ```
706
+
482
707
  ## License
483
708
 
484
709
  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[]>;