rsbuild-plugin-react-router 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present Bytedance, Inc. and its affiliates.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,484 @@
1
+ # @rsbuild/plugin-react-router
2
+
3
+ <p align="center">
4
+ <a href="https://rsbuild.dev" target="blank"><img src="https://github.com/web-infra-dev/rsbuild/assets/7237365/84abc13e-b620-468f-a90b-dbf28e7e9427" alt="Rsbuild Logo" /></a>
5
+ </p>
6
+
7
+ A Rsbuild plugin that provides seamless integration with React Router, supporting both client-side routing and server-side rendering (SSR).
8
+
9
+ ## Features
10
+
11
+ - 🚀 Zero-config setup with sensible defaults
12
+ - 🔄 Automatic route generation from file system
13
+ - 🖥️ Server-Side Rendering (SSR) support
14
+ - 📱 Client-side navigation
15
+ - 🛠️ TypeScript support out of the box
16
+ - 🔧 Customizable configuration
17
+ - 🎯 Support for route-level code splitting
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @rsbuild/plugin-react-router
23
+ # or
24
+ yarn add @rsbuild/plugin-react-router
25
+ # or
26
+ pnpm add @rsbuild/plugin-react-router
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ Add the plugin to your `rsbuild.config.ts`:
32
+
33
+ ```ts
34
+ import { defineConfig } from '@rsbuild/core';
35
+ import { pluginReactRouter } from '@rsbuild/plugin-react-router';
36
+ import { pluginReact } from '@rsbuild/plugin-react';
37
+
38
+ export default defineConfig(() => {
39
+ return {
40
+ plugins: [
41
+ pluginReactRouter({
42
+ // Optional: Enable custom server mode
43
+ customServer: false,
44
+ // Optional: Specify server output format
45
+ serverOutput: "commonjs",
46
+ //Optional: enable experimental support for module federation
47
+ federation: false
48
+ }),
49
+ pluginReact()
50
+ ],
51
+ };
52
+ });
53
+ ```
54
+
55
+ ## Configuration
56
+
57
+ The plugin uses a two-part configuration system:
58
+
59
+ 1. **Plugin Options** (in `rsbuild.config.ts`):
60
+ ```ts
61
+ pluginReactRouter({
62
+ /**
63
+ * Whether to disable automatic middleware setup for custom server implementation.
64
+ * Enable this when you want to handle server setup manually.
65
+ * @default false
66
+ */
67
+ customServer?: boolean,
68
+
69
+ /**
70
+ * Specify the output format for server-side code.
71
+ * Options: "commonjs" | "module"
72
+ * @default "module"
73
+ */
74
+ serverOutput?: "commonjs" | "module"
75
+ /**
76
+ * Enable experimental support for module federation
77
+ * @default false
78
+ */
79
+ federation?: boolean
80
+ })
81
+ ```
82
+
83
+ 2. **React Router Configuration** (in `react-router.config.ts`):
84
+ ```ts
85
+ import type { Config } from '@react-router/dev/config';
86
+
87
+ export default {
88
+ /**
89
+ * Whether to enable Server-Side Rendering (SSR) support.
90
+ * @default true
91
+ */
92
+ ssr: true,
93
+
94
+ /**
95
+ * Build directory for output files
96
+ * @default 'build'
97
+ */
98
+ buildDirectory: 'dist',
99
+
100
+ /**
101
+ * Application source directory
102
+ * @default 'app'
103
+ */
104
+ appDirectory: 'app',
105
+
106
+ /**
107
+ * Base URL path
108
+ * @default '/'
109
+ */
110
+ basename: '/my-app',
111
+ } satisfies Config;
112
+ ```
113
+
114
+ All configuration options are optional and will use sensible defaults if not specified.
115
+
116
+ ### Default Configuration Values
117
+
118
+ If no configuration is provided, the following defaults will be used:
119
+
120
+ ```ts
121
+ // Plugin defaults (rsbuild.config.ts)
122
+ {
123
+ customServer: false
124
+ }
125
+
126
+ // Router defaults (react-router.config.ts)
127
+ {
128
+ ssr: true,
129
+ buildDirectory: 'build',
130
+ appDirectory: 'app',
131
+ basename: '/'
132
+ }
133
+ ```
134
+
135
+ ### Route Configuration
136
+
137
+ Routes can be defined in `app/routes.ts` using the helper functions from `@react-router/dev/routes`:
138
+
139
+ ```ts
140
+ import {
141
+ type RouteConfig,
142
+ index,
143
+ layout,
144
+ prefix,
145
+ route,
146
+ } from '@react-router/dev/routes';
147
+
148
+ export default [
149
+ // Index route for the home page
150
+ index('routes/home.tsx'),
151
+
152
+ // Regular route
153
+ route('about', 'routes/about.tsx'),
154
+
155
+ // Nested routes with a layout
156
+ layout('routes/docs/layout.tsx', [
157
+ index('routes/docs/index.tsx'),
158
+ route('getting-started', 'routes/docs/getting-started.tsx'),
159
+ route('advanced', 'routes/docs/advanced.tsx'),
160
+ ]),
161
+
162
+ // Routes with dynamic segments
163
+ ...prefix('projects', [
164
+ index('routes/projects/index.tsx'),
165
+ layout('routes/projects/layout.tsx', [
166
+ route(':projectId', 'routes/projects/project.tsx'),
167
+ route(':projectId/edit', 'routes/projects/edit.tsx'),
168
+ ]),
169
+ ]),
170
+ ] satisfies RouteConfig;
171
+ ```
172
+
173
+ The plugin provides several helper functions for defining routes:
174
+ - `index()` - Creates an index route
175
+ - `route()` - Creates a regular route with a path
176
+ - `layout()` - Creates a layout route with nested children
177
+ - `prefix()` - Adds a URL prefix to a group of routes
178
+
179
+ ### Route Components
180
+
181
+ Route components support the following exports:
182
+
183
+ #### Client-side Exports
184
+ - `default` - The route component
185
+ - `ErrorBoundary` - Error boundary component
186
+ - `HydrateFallback` - Loading component during hydration
187
+ - `Layout` - Layout component
188
+ - `clientLoader` - Client-side data loading
189
+ - `clientAction` - Client-side form actions
190
+ - `handle` - Route handle
191
+ - `links` - Prefetch links
192
+ - `meta` - Route meta data
193
+ - `shouldRevalidate` - Revalidation control
194
+
195
+ #### Server-side Exports
196
+ - `loader` - Server-side data loading
197
+ - `action` - Server-side form actions
198
+ - `headers` - HTTP headers
199
+
200
+ ## Custom Server Setup
201
+
202
+ The plugin supports two ways to handle server-side rendering:
203
+
204
+ 1. **Default Server Setup**: By default, the plugin automatically sets up the necessary middleware for SSR.
205
+
206
+ 2. **Custom Server Setup**: For more control, you can disable the automatic middleware setup by enabling custom server mode:
207
+
208
+ ```ts
209
+ // rsbuild.config.ts
210
+ import { defineConfig } from '@rsbuild/core';
211
+ import { pluginReactRouter } from '@rsbuild/plugin-react-router';
212
+ import { pluginReact } from '@rsbuild/plugin-react';
213
+
214
+ export default defineConfig(() => {
215
+ return {
216
+ plugins: [
217
+ pluginReactRouter({
218
+ customServer: true
219
+ }),
220
+ pluginReact()
221
+ ],
222
+ };
223
+ });
224
+ ```
225
+
226
+ When using a custom server, you'll need to:
227
+
228
+ 1. Create a server handler (`server/index.ts`):
229
+ ```ts
230
+ import { createRequestHandler } from '@react-router/express';
231
+
232
+ export const app = createRequestHandler({
233
+ build: () => import('virtual/react-router/server-build'),
234
+ getLoadContext() {
235
+ // Add custom context available to your loaders/actions
236
+ return {
237
+ // ... your custom context
238
+ };
239
+ },
240
+ });
241
+ ```
242
+
243
+ 2. Set up your server entry point (`server.js`):
244
+ ```js
245
+ import { createRsbuild, loadConfig } from '@rsbuild/core';
246
+ import express from 'express';
247
+ import path from 'path';
248
+ import { fileURLToPath } from 'url';
249
+
250
+ const __filename = fileURLToPath(import.meta.url);
251
+ const __dirname = path.dirname(__filename);
252
+
253
+ const app = express();
254
+ const isDev = process.env.NODE_ENV !== 'production';
255
+
256
+ async function startServer() {
257
+ if (isDev) {
258
+ const config = await loadConfig();
259
+ const rsbuild = await createRsbuild({
260
+ rsbuildConfig: config.content,
261
+ });
262
+ const devServer = await rsbuild.createDevServer();
263
+ app.use(devServer.middlewares);
264
+
265
+ app.use(async (req, res, next) => {
266
+ try {
267
+ const bundle = await devServer.environments.node.loadBundle('app');
268
+ await bundle.app(req, res, next);
269
+ } catch (e) {
270
+ next(e);
271
+ }
272
+ });
273
+
274
+ const port = Number.parseInt(process.env.PORT || '3000', 10);
275
+ const server = app.listen(port, () => {
276
+ console.log(`Development server is running on http://localhost:${port}`);
277
+ devServer.afterListen();
278
+ });
279
+ devServer.connectWebSocket({ server });
280
+ } else {
281
+ // Production mode
282
+ app.use(express.static(path.join(__dirname, 'build/client'), {
283
+ index: false
284
+ }));
285
+
286
+ // Load the server bundle
287
+ const serverBundle = await import('./build/server/static/js/app.js');
288
+ // Mount the server app after static file handling
289
+ app.use(async (req, res, next) => {
290
+ try {
291
+ await serverBundle.default.app(req, res, next);
292
+ } catch (e) {
293
+ next(e);
294
+ }
295
+ });
296
+
297
+ const port = Number.parseInt(process.env.PORT || '3000', 10);
298
+ app.listen(port, () => {
299
+ console.log(`Production server is running on http://localhost:${port}`);
300
+ });
301
+ }
302
+ }
303
+
304
+ startServer().catch(console.error);
305
+ ```
306
+
307
+ 3. Update your `package.json` scripts:
308
+ ```json
309
+ {
310
+ "scripts": {
311
+ "dev": "node server.js",
312
+ "build": "rsbuild build",
313
+ "start": "NODE_ENV=production node server.js"
314
+ }
315
+ }
316
+ ```
317
+
318
+ The custom server setup allows you to:
319
+ - Add custom middleware
320
+ - Handle API routes
321
+ - Integrate with databases
322
+ - Implement custom authentication
323
+ - Add server-side caching
324
+ - And more!
325
+
326
+ ## Cloudflare Workers Deployment
327
+
328
+ To deploy your React Router app to Cloudflare Workers:
329
+
330
+ 1. **Configure Rsbuild** (`rsbuild.config.ts`):
331
+ ```ts
332
+ import { defineConfig } from '@rsbuild/core';
333
+ import { pluginReact } from '@rsbuild/plugin-react';
334
+ import { pluginReactRouter } from '@rsbuild/plugin-react-router';
335
+
336
+ export default defineConfig({
337
+ environments: {
338
+ node: {
339
+ performance: {
340
+ chunkSplit: { strategy: 'all-in-one' },
341
+ },
342
+ tools: {
343
+ rspack: {
344
+ experiments: { outputModule: true },
345
+ externalsType: 'module',
346
+ output: {
347
+ chunkFormat: 'module',
348
+ chunkLoading: 'import',
349
+ workerChunkLoading: 'import',
350
+ wasmLoading: 'fetch',
351
+ library: { type: 'module' },
352
+ module: true,
353
+ },
354
+ resolve: {
355
+ conditionNames: ['workerd', 'worker', 'browser', 'import', 'require'],
356
+ },
357
+ },
358
+ },
359
+ },
360
+ },
361
+ plugins: [pluginReactRouter({customServer: true}), pluginReact()],
362
+ });
363
+ ```
364
+
365
+ 2. **Configure Wrangler** (`wrangler.toml`):
366
+ ```toml
367
+ workers_dev = true
368
+ name = "my-react-router-worker"
369
+ compatibility_date = "2024-11-18"
370
+ main = "./build/server/static/js/app.js"
371
+ assets = { directory = "./build/client/" }
372
+
373
+ [vars]
374
+ VALUE_FROM_CLOUDFLARE = "Hello from Cloudflare"
375
+
376
+ # Optional build configuration
377
+ # [build]
378
+ # command = "npm run build"
379
+ # watch_dir = "app"
380
+ ```
381
+
382
+ 3. **Create Worker Entry** (`server/index.ts`):
383
+ ```ts
384
+ import { createRequestHandler } from 'react-router';
385
+
386
+ declare global {
387
+ interface CloudflareEnvironment extends Env {}
388
+ interface ImportMeta {
389
+ env: {
390
+ MODE: string;
391
+ };
392
+ }
393
+ }
394
+
395
+ declare module 'react-router' {
396
+ export interface AppLoadContext {
397
+ cloudflare: {
398
+ env: CloudflareEnvironment;
399
+ ctx: ExecutionContext;
400
+ };
401
+ }
402
+ }
403
+
404
+ // @ts-expect-error - virtual module provided by React Router at build time
405
+ import * as serverBuild from 'virtual/react-router/server-build';
406
+
407
+ const requestHandler = createRequestHandler(serverBuild, import.meta.env.MODE);
408
+
409
+ export default {
410
+ fetch(request, env, ctx) {
411
+ return requestHandler(request, {
412
+ cloudflare: { env, ctx },
413
+ });
414
+ },
415
+ } satisfies ExportedHandler<CloudflareEnvironment>;
416
+ ```
417
+
418
+ 4. **Update Package Dependencies**:
419
+ ```json
420
+ {
421
+ "dependencies": {
422
+ "@react-router/node": "^7.1.3",
423
+ "@react-router/serve": "^7.1.3",
424
+ "react-router": "^7.1.3"
425
+ },
426
+ "devDependencies": {
427
+ "@cloudflare/workers-types": "^4.20241112.0",
428
+ "@react-router/cloudflare": "^7.1.3",
429
+ "@react-router/dev": "^7.1.3",
430
+ "wrangler": "^3.106.0"
431
+ }
432
+ }
433
+ ```
434
+
435
+ 5. **Setup Deployment Scripts** (`package.json`):
436
+ ```json
437
+ {
438
+ "scripts": {
439
+ "build": "rsbuild build",
440
+ "deploy": "npm run build && wrangler deploy",
441
+ "dev": "rsbuild dev",
442
+ "start": "wrangler dev"
443
+ }
444
+ }
445
+ ```
446
+
447
+ ### Key Configuration Notes:
448
+
449
+ - The `workers_dev = true` setting enables deployment to workers.dev subdomain
450
+ - `main` points to your Worker's entry point in the build output
451
+ - `assets` directory specifies where your static client files are located
452
+ - Environment variables can be set in the `[vars]` section
453
+ - The `compatibility_date` should be kept up to date
454
+ - TypeScript types are provided via `@cloudflare/workers-types`
455
+ - Development can be done locally using `wrangler dev`
456
+ - Deployment is handled through `wrangler deploy`
457
+
458
+ ### Development Workflow:
459
+
460
+ 1. Local Development:
461
+ ```bash
462
+ # Start local development server
463
+ npm run dev
464
+ # or
465
+ npm start
466
+ ```
467
+
468
+ 2. Production Deployment:
469
+ ```bash
470
+ # Build and deploy
471
+ npm run deploy
472
+ ```
473
+
474
+ ## Development
475
+
476
+ The plugin automatically:
477
+ - Runs type generation during development and build
478
+ - Sets up development server with live reload
479
+ - Handles route-based code splitting
480
+ - Manages client and server builds
481
+
482
+ ## License
483
+
484
+ MIT
@@ -0,0 +1,8 @@
1
+ import type { types as Babel } from '@babel/core';
2
+ import { type ParseResult, parse } from '@babel/parser';
3
+ import type { NodePath } from '@babel/traverse';
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;
7
+ export { traverse, generate, parse, t };
8
+ export type { Babel, NodePath, ParseResult };
@@ -0,0 +1,28 @@
1
+ export declare const PLUGIN_NAME = "rsbuild:react-router";
2
+ export declare const JS_EXTENSIONS: readonly [".tsx", ".ts", ".jsx", ".js", ".mjs"];
3
+ export declare const JS_LOADERS: {
4
+ readonly '.ts': "ts";
5
+ readonly '.tsx': "tsx";
6
+ readonly '.js': "js";
7
+ readonly '.jsx': "jsx";
8
+ };
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 NAMED_COMPONENT_EXPORTS: readonly ["HydrateFallback", "ErrorBoundary"];
12
+ export declare const SERVER_EXPORTS: {
13
+ readonly loader: "loader";
14
+ readonly action: "action";
15
+ readonly headers: "headers";
16
+ };
17
+ export declare const CLIENT_EXPORTS: {
18
+ readonly clientAction: "clientAction";
19
+ readonly clientLoader: "clientLoader";
20
+ readonly default: "default";
21
+ readonly ErrorBoundary: "ErrorBoundary";
22
+ readonly handle: "handle";
23
+ readonly HydrateFallback: "HydrateFallback";
24
+ readonly Layout: "Layout";
25
+ readonly links: "links";
26
+ readonly meta: "meta";
27
+ readonly shouldRevalidate: "shouldRevalidate";
28
+ };
@@ -0,0 +1,3 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export type DevServerMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: any) => void) => Promise<void>;
3
+ export declare const createDevServerMiddleware: (server: any) => DevServerMiddleware;