UNPKG

rsbuild-plugin-react-router

Version:
485 lines (404 loc) 11.9 kB
# rsbuild-plugin-react-router <p align="center"> <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> </p> A Rsbuild plugin that provides seamless integration with React Router, supporting both client-side routing and server-side rendering (SSR). ## Features - 🚀 Zero-config setup with sensible defaults - 🔄 Automatic route generation from file system - 🖥️ Server-Side Rendering (SSR) support - 📱 Client-side navigation - 🛠️ TypeScript support out of the box - 🔧 Customizable configuration - 🎯 Support for route-level code splitting ## Installation ```bash npm install rsbuild-plugin-react-router # or yarn add rsbuild-plugin-react-router # or pnpm add rsbuild-plugin-react-router ``` ## Usage Add the plugin to your `rsbuild.config.ts`: ```ts import { defineConfig } from '@rsbuild/core'; import { pluginReactRouter } from 'rsbuild-plugin-react-router'; import { pluginReact } from '@rsbuild/plugin-react'; export default defineConfig(() => { return { plugins: [ pluginReactRouter({ // Optional: Enable custom server mode customServer: false, // Optional: Specify server output format serverOutput: "commonjs", //Optional: enable experimental support for module federation federation: false }), pluginReact() ], }; }); ``` ## Configuration The plugin uses a two-part configuration system: 1. **Plugin Options** (in `rsbuild.config.ts`): ```ts pluginReactRouter({ /** * Whether to disable automatic middleware setup for custom server implementation. * Enable this when you want to handle server setup manually. * @default false */ customServer?: boolean, /** * Specify the output format for server-side code. * Options: "commonjs" | "module" * @default "module" */ serverOutput?: "commonjs" | "module" /** * Enable experimental support for module federation * @default false */ federation?: boolean }) ``` 2. **React Router Configuration** (in `react-router.config.ts`): ```ts import type { Config } from '@react-router/dev/config'; export default { /** * Whether to enable Server-Side Rendering (SSR) support. * @default true */ ssr: true, /** * Build directory for output files * @default 'build' */ buildDirectory: 'dist', /** * Application source directory * @default 'app' */ appDirectory: 'app', /** * Base URL path * @default '/' */ basename: '/my-app', } satisfies Config; ``` All configuration options are optional and will use sensible defaults if not specified. ### Default Configuration Values If no configuration is provided, the following defaults will be used: ```ts // Plugin defaults (rsbuild.config.ts) { customServer: false } // Router defaults (react-router.config.ts) { ssr: true, buildDirectory: 'build', appDirectory: 'app', basename: '/' } ``` ### Route Configuration Routes can be defined in `app/routes.ts` using the helper functions from `@react-router/dev/routes`: ```ts import { type RouteConfig, index, layout, prefix, route, } from '@react-router/dev/routes'; export default [ // Index route for the home page index('routes/home.tsx'), // Regular route route('about', 'routes/about.tsx'), // Nested routes with a layout layout('routes/docs/layout.tsx', [ index('routes/docs/index.tsx'), route('getting-started', 'routes/docs/getting-started.tsx'), route('advanced', 'routes/docs/advanced.tsx'), ]), // Routes with dynamic segments ...prefix('projects', [ index('routes/projects/index.tsx'), layout('routes/projects/layout.tsx', [ route(':projectId', 'routes/projects/project.tsx'), route(':projectId/edit', 'routes/projects/edit.tsx'), ]), ]), ] satisfies RouteConfig; ``` The plugin provides several helper functions for defining routes: - `index()` - Creates an index route - `route()` - Creates a regular route with a path - `layout()` - Creates a layout route with nested children - `prefix()` - Adds a URL prefix to a group of routes ### Route Components Route components support the following exports: #### Client-side Exports - `default` - The route component - `ErrorBoundary` - Error boundary component - `HydrateFallback` - Loading component during hydration - `Layout` - Layout component - `clientLoader` - Client-side data loading - `clientAction` - Client-side form actions - `handle` - Route handle - `links` - Prefetch links - `meta` - Route meta data - `shouldRevalidate` - Revalidation control #### Server-side Exports - `loader` - Server-side data loading - `action` - Server-side form actions - `headers` - HTTP headers ## Custom Server Setup The plugin supports two ways to handle server-side rendering: 1. **Default Server Setup**: By default, the plugin automatically sets up the necessary middleware for SSR. 2. **Custom Server Setup**: For more control, you can disable the automatic middleware setup by enabling custom server mode: ```ts // rsbuild.config.ts import { defineConfig } from '@rsbuild/core'; import { pluginReactRouter } from 'rsbuild-plugin-react-router'; import { pluginReact } from '@rsbuild/plugin-react'; export default defineConfig(() => { return { plugins: [ pluginReactRouter({ customServer: true }), pluginReact() ], }; }); ``` When using a custom server, you'll need to: 1. Create a server handler (`server/index.ts`): ```ts import { createRequestHandler } from '@react-router/express'; export const app = createRequestHandler({ build: () => import('virtual/react-router/server-build'), getLoadContext() { // Add custom context available to your loaders/actions return { // ... your custom context }; }, }); ``` 2. Set up your server entry point (`server.js`): ```js import { createRsbuild, loadConfig } from '@rsbuild/core'; import express from 'express'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const isDev = process.env.NODE_ENV !== 'production'; async function startServer() { if (isDev) { const config = await loadConfig(); const rsbuild = await createRsbuild({ rsbuildConfig: config.content, }); const devServer = await rsbuild.createDevServer(); app.use(devServer.middlewares); app.use(async (req, res, next) => { try { const bundle = await devServer.environments.node.loadBundle('app'); await bundle.app(req, res, next); } catch (e) { next(e); } }); const port = Number.parseInt(process.env.PORT || '3000', 10); const server = app.listen(port, () => { console.log(`Development server is running on http://localhost:${port}`); devServer.afterListen(); }); devServer.connectWebSocket({ server }); } else { // Production mode app.use(express.static(path.join(__dirname, 'build/client'), { index: false })); // Load the server bundle const serverBundle = await import('./build/server/static/js/app.js'); // Mount the server app after static file handling app.use(async (req, res, next) => { try { await serverBundle.default.app(req, res, next); } catch (e) { next(e); } }); const port = Number.parseInt(process.env.PORT || '3000', 10); app.listen(port, () => { console.log(`Production server is running on http://localhost:${port}`); }); } } startServer().catch(console.error); ``` 3. Update your `package.json` scripts: ```json { "scripts": { "dev": "node server.js", "build": "rsbuild build", "start": "NODE_ENV=production node server.js" } } ``` The custom server setup allows you to: - Add custom middleware - Handle API routes - Integrate with databases - Implement custom authentication - Add server-side caching - And more! ## Cloudflare Workers Deployment To deploy your React Router app to Cloudflare Workers: 1. **Configure Rsbuild** (`rsbuild.config.ts`): ```ts import { defineConfig } from '@rsbuild/core'; import { pluginReact } from '@rsbuild/plugin-react'; import { pluginReactRouter } from 'rsbuild-plugin-react-router'; export default defineConfig({ environments: { node: { performance: { chunkSplit: { strategy: 'all-in-one' }, }, tools: { rspack: { experiments: { outputModule: true }, externalsType: 'module', output: { chunkFormat: 'module', chunkLoading: 'import', workerChunkLoading: 'import', wasmLoading: 'fetch', library: { type: 'module' }, module: true, }, resolve: { conditionNames: ['workerd', 'worker', 'browser', 'import', 'require'], }, }, }, }, }, plugins: [pluginReactRouter({customServer: true}), pluginReact()], }); ``` 2. **Configure Wrangler** (`wrangler.toml`): ```toml workers_dev = true name = "my-react-router-worker" compatibility_date = "2024-11-18" main = "./build/server/static/js/app.js" assets = { directory = "./build/client/" } [vars] VALUE_FROM_CLOUDFLARE = "Hello from Cloudflare" # Optional build configuration # [build] # command = "npm run build" # watch_dir = "app" ``` 3. **Create Worker Entry** (`server/index.ts`): ```ts import { createRequestHandler } from 'react-router'; declare global { interface CloudflareEnvironment extends Env {} interface ImportMeta { env: { MODE: string; }; } } declare module 'react-router' { export interface AppLoadContext { cloudflare: { env: CloudflareEnvironment; ctx: ExecutionContext; }; } } // @ts-expect-error - virtual module provided by React Router at build time import * as serverBuild from 'virtual/react-router/server-build'; const requestHandler = createRequestHandler(serverBuild, import.meta.env.MODE); export default { fetch(request, env, ctx) { return requestHandler(request, { cloudflare: { env, ctx }, }); }, } satisfies ExportedHandler<CloudflareEnvironment>; ``` 4. **Update Package Dependencies**: ```json { "dependencies": { "@react-router/node": "^7.1.3", "@react-router/serve": "^7.1.3", "react-router": "^7.1.3" }, "devDependencies": { "@cloudflare/workers-types": "^4.20241112.0", "@react-router/cloudflare": "^7.1.3", "@react-router/dev": "^7.1.3", "wrangler": "^3.106.0" } } ``` 5. **Setup Deployment Scripts** (`package.json`): ```json { "scripts": { "build": "rsbuild build", "deploy": "npm run build && wrangler deploy", "dev": "rsbuild dev", "start": "wrangler dev" } } ``` ### Key Configuration Notes: - The `workers_dev = true` setting enables deployment to workers.dev subdomain - `main` points to your Worker's entry point in the build output - `assets` directory specifies where your static client files are located - Environment variables can be set in the `[vars]` section - The `compatibility_date` should be kept up to date - TypeScript types are provided via `@cloudflare/workers-types` - Development can be done locally using `wrangler dev` - Deployment is handled through `wrangler deploy` ### Development Workflow: 1. Local Development: ```bash # Start local development server npm run dev # or npm start ``` 2. Production Deployment: ```bash # Build and deploy npm run deploy ``` ## Development The plugin automatically: - Runs type generation during development and build - Sets up development server with live reload - Handles route-based code splitting - Manages client and server builds ## License MIT