From 7d2914bca026accf51c0d79d032384d949d09a15 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Fri, 3 Jul 2026 21:03:44 +0000 Subject: [PATCH] fix: migrate dev middleware registration to server.setup Rsbuild 2.1 deprecates `dev.setupMiddlewares` in favor of `server.setup`. Register the React Router dev middleware through a `server.setup` function that returns a post-built-in-middlewares callback, preserving the ordering guarantee that static assets are served before the request handler. Co-Authored-By: Claude Fable 5 --- src/dev-server.ts | 23 ++++++++++++++++++++ src/index.ts | 26 +++++++++++----------- tests/features.test.ts | 49 +++++++++++++++++++++++++++++++++++++++++- tests/setup.ts | 1 - 4 files changed, 84 insertions(+), 15 deletions(-) diff --git a/src/dev-server.ts b/src/dev-server.ts index f404a233..3b0e3281 100644 --- a/src/dev-server.ts +++ b/src/dev-server.ts @@ -1,6 +1,12 @@ import type { IncomingMessage, ServerResponse } from 'node:http'; +import type { RsbuildConfig } from '@rsbuild/core'; import type { ServerBuild } from 'react-router'; +export type ServerSetup = Exclude< + NonNullable['setup']>, + unknown[] +>; + export type DevServerMiddleware = ( req: IncomingMessage, res: ServerResponse, @@ -63,3 +69,20 @@ export const createDevServerMiddleware = ( } }; }; + +export const createReactRouterDevServerSetup = ({ + loadBuild, +}: { + loadBuild: BuildProvider; +}): ServerSetup => + function reactRouterDevServerSetup(context) { + if (context.action !== 'dev') { + return; + } + // The returned callback runs after Rsbuild registers its built-in + // middlewares, so static assets are served before the React Router + // request handler. + return () => { + context.server.middlewares.use(createDevServerMiddleware({ loadBuild })); + }; + }; diff --git a/src/index.ts b/src/index.ts index 81d28804..2f5a5100 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,7 +18,7 @@ import { PLUGIN_NAME, } from './constants.js'; import { guardReactRouterLazyCompilation } from './lazy-compilation.js'; -import { createDevServerMiddleware } from './dev-server.js'; +import { createReactRouterDevServerSetup } from './dev-server.js'; import { generateWithProps, findEntryFile, @@ -768,19 +768,19 @@ export const pluginReactRouter = ( writeToDisk: true, ...lazyCompilation, watchFiles: mergeWatchFiles(config.dev?.watchFiles, routeWatchFiles), - setupMiddlewares: - pluginOptions.customServer || !ssr - ? [] - : [ - middlewares => { - middlewares.push( - createDevServerMiddleware({ - loadBuild: devRuntime.createBuildLoader(), - }) - ); - }, - ], }, + ...(pluginOptions.customServer || !ssr + ? {} + : { + server: { + setup: [ + createReactRouterDevServerSetup({ + // Lazy: the dev runtime binding does not exist yet here. + loadBuild: () => devRuntime.createBuildLoader()(), + }), + ], + }, + }), tools: { rspack: { plugins: [vmodPlugin], diff --git a/tests/features.test.ts b/tests/features.test.ts index b7e25807..a247b405 100644 --- a/tests/features.test.ts +++ b/tests/features.test.ts @@ -6,6 +6,20 @@ import path from 'node:path'; import { pluginReactRouter } from '../src'; import { getVirtualModuleFilePath } from '../src/virtual-modules'; +type ReactRouterTestGlobal = typeof globalThis & { + __reactRouterTestConfig?: unknown; +}; + +const testGlobal = globalThis as ReactRouterTestGlobal; + +const getServerSetupNames = (config: { + server?: { setup?: unknown }; +}): string[] => + [config.server?.setup] + .flat() + .filter((fn): fn is (...args: unknown[]) => unknown => Boolean(fn)) + .map(fn => fn.name); + describe('pluginReactRouter', () => { describe('basic configuration', () => { it('should apply default options when no options provided', async () => { @@ -22,6 +36,21 @@ describe('pluginReactRouter', () => { expect(config.dev.writeToDisk).toBe(true); }); + it('should register the dev server middleware via server.setup', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + // The deprecated `dev.setupMiddlewares` API must stay untouched. + expect(config.dev.setupMiddlewares).toBeUndefined(); + expect(getServerSetupNames(config)).toContain( + 'reactRouterDevServerSetup' + ); + }); + it('should respect customServer option', async () => { const rsbuild = await createStubRsbuild({ rsbuildConfig: {}, @@ -30,7 +59,25 @@ describe('pluginReactRouter', () => { rsbuild.addPlugins([pluginReactRouter({ customServer: true })]); const config = await rsbuild.unwrapConfig(); - expect(config.dev.setupMiddlewares).toEqual([]); + expect(config.dev.setupMiddlewares).toBeUndefined(); + expect(getServerSetupNames(config)).not.toContain( + 'reactRouterDevServerSetup' + ); + }); + + it('should not register the dev server middleware in SPA mode', async () => { + testGlobal.__reactRouterTestConfig = { ssr: false }; + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + expect(config.dev.setupMiddlewares).toBeUndefined(); + expect(getServerSetupNames(config)).not.toContain( + 'reactRouterDevServerSetup' + ); }); it('should configure server output format correctly', async () => { diff --git a/tests/setup.ts b/tests/setup.ts index ce03b4c8..4b5f77df 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -105,7 +105,6 @@ rstest.mock('@scripts/test-helper', () => ({ hmr: true, liveReload: true, writeToDisk: false, - setupMiddlewares: [], }, environments: { web: {