From a77ff4f2b45c2cf7ebae3f783c24a222979cbd45 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 2 Feb 2026 17:17:41 -0800 Subject: [PATCH 1/4] feat: allow rspack hmr in dev --- .changeset/rspack-hmr-for-ssr-dev.md | 5 +++++ src/index.ts | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .changeset/rspack-hmr-for-ssr-dev.md diff --git a/.changeset/rspack-hmr-for-ssr-dev.md b/.changeset/rspack-hmr-for-ssr-dev.md new file mode 100644 index 0000000..8a4ee60 --- /dev/null +++ b/.changeset/rspack-hmr-for-ssr-dev.md @@ -0,0 +1,5 @@ +--- +"rsbuild-plugin-react-router": patch +--- + +Enable Rsbuild/Rspack HMR by not forcing `dev.hmr=false` in the React Router plugin. diff --git a/src/index.ts b/src/index.ts index b3a8772..c51fe34 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1078,8 +1078,6 @@ export const pluginReactRouter = ( }, dev: { writeToDisk: true, - hmr: false, - liveReload: true, // Only add SSR middleware if SSR is enabled and not using a custom server // In SPA mode (ssr: false), we just serve static files from the client build setupMiddlewares: From dba352c2043881378fb400fd241a61dac0be7308 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 2 Feb 2026 17:18:45 -0800 Subject: [PATCH 2/4] docs: clarify changeset for ESM HMR --- .changeset/rspack-hmr-for-ssr-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/rspack-hmr-for-ssr-dev.md b/.changeset/rspack-hmr-for-ssr-dev.md index 8a4ee60..6efc506 100644 --- a/.changeset/rspack-hmr-for-ssr-dev.md +++ b/.changeset/rspack-hmr-for-ssr-dev.md @@ -2,4 +2,4 @@ "rsbuild-plugin-react-router": patch --- -Enable Rsbuild/Rspack HMR by not forcing `dev.hmr=false` in the React Router plugin. +Enable Rspack HMR for ESM server outputs by not forcing `dev.hmr=false` in the React Router plugin. From 069a9f18301e1cc7327247221dfac2f76e11cffa Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 2 Feb 2026 17:25:22 -0800 Subject: [PATCH 3/4] test: stop asserting dev hmr defaults --- tests/features.test.ts | 2 -- tests/index.test.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/features.test.ts b/tests/features.test.ts index 9a5afe3..7486c69 100644 --- a/tests/features.test.ts +++ b/tests/features.test.ts @@ -12,8 +12,6 @@ describe('pluginReactRouter', () => { rsbuild.addPlugins([pluginReactRouter()]); const config = await rsbuild.unwrapConfig(); - expect(config.dev.hmr).toBe(false); - expect(config.dev.liveReload).toBe(true); expect(config.dev.writeToDisk).toBe(true); }); diff --git a/tests/index.test.ts b/tests/index.test.ts index d8288b6..1afbaa2 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -11,8 +11,6 @@ describe('pluginReactRouter', () => { rsbuild.addPlugins([pluginReactRouter()]); const config = await rsbuild.unwrapConfig(); - expect(config.dev.hmr).toBe(false); - expect(config.dev.liveReload).toBe(true); expect(config.dev.writeToDisk).toBe(true); }); From 9c8e22b2de6dcd790798e49a918f74536c8366a0 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 2 Feb 2026 18:27:53 -0800 Subject: [PATCH 4/4] test: make dev hmr defaults assertions effective --- tests/features.test.ts | 3 +++ tests/index.test.ts | 3 +++ tests/setup.ts | 54 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/tests/features.test.ts b/tests/features.test.ts index 7486c69..ad1e3bf 100644 --- a/tests/features.test.ts +++ b/tests/features.test.ts @@ -12,6 +12,9 @@ describe('pluginReactRouter', () => { rsbuild.addPlugins([pluginReactRouter()]); const config = await rsbuild.unwrapConfig(); + // The plugin should not override Rsbuild's HMR defaults. + expect(config.dev.hmr).toBe(true); + expect(config.dev.liveReload).toBe(true); expect(config.dev.writeToDisk).toBe(true); }); diff --git a/tests/index.test.ts b/tests/index.test.ts index 1afbaa2..47c3e4b 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -11,6 +11,9 @@ describe('pluginReactRouter', () => { rsbuild.addPlugins([pluginReactRouter()]); const config = await rsbuild.unwrapConfig(); + // The plugin should not override Rsbuild's HMR defaults. + expect(config.dev.hmr).toBe(true); + expect(config.dev.liveReload).toBe(true); expect(config.dev.writeToDisk).toBe(true); }); diff --git a/tests/setup.ts b/tests/setup.ts index d169f12..a860a14 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -54,9 +54,11 @@ rstest.mock('@scripts/test-helper', () => ({ createStubRsbuild: rstest.fn().mockImplementation(async ({ rsbuildConfig = {} } = {}) => { const baseConfig = { dev: { - hmr: false, + // Match Rsbuild defaults so plugin changes are observable in tests. + // (Historically the plugin forced `hmr: false` / `liveReload: true`.) + hmr: true, liveReload: true, - writeToDisk: true, + writeToDisk: false, setupMiddlewares: [], }, environments: { @@ -98,17 +100,24 @@ rstest.mock('@scripts/test-helper', () => ({ ], }; - const mergedConfig = deepMerge(baseConfig, rsbuildConfig); + let mergedConfig = deepMerge(baseConfig, rsbuildConfig); - return { + const mergeRsbuildConfig = (a: any, b: any) => deepMerge(a, b); + const pending: Promise[] = []; + + const stub: any = { addPlugins: rstest.fn(), - unwrapConfig: rstest.fn().mockResolvedValue(mergedConfig), + unwrapConfig: rstest.fn(), processAssets: rstest.fn(), onBeforeStartDevServer: rstest.fn(), onBeforeBuild: rstest.fn(), onAfterBuild: rstest.fn(), + getNormalizedConfig: rstest.fn().mockImplementation(() => mergedConfig), modifyRsbuildConfig: rstest.fn(), onAfterEnvironmentCompile: rstest.fn(), + // Keep as a spy-only hook; tests in this repo assert against the merged + // Rsbuild config (modifyRsbuildConfig), not post-normalization environment + // mutations. modifyEnvironmentConfig: rstest.fn(), transform: rstest.fn(), logger: { @@ -118,6 +127,7 @@ rstest.mock('@scripts/test-helper', () => ({ }, context: { rootPath: '/Users/bytedance/dev/rsbuild-plugin-react-router', + action: 'dev', }, compiler: { webpack: { @@ -127,5 +137,39 @@ rstest.mock('@scripts/test-helper', () => ({ }, }, }; + + stub.modifyRsbuildConfig.mockImplementation((arg: any) => { + const handler = typeof arg === 'function' ? arg : arg?.handler; + if (typeof handler !== 'function') return; + + const res = handler(mergedConfig, { mergeRsbuildConfig }); + if (res && typeof res.then === 'function') { + const p = res.then((next: any) => { + if (next) mergedConfig = next; + return next; + }); + pending.push(p); + return p; + } + if (res) mergedConfig = res; + return res; + }); + + // In Rsbuild, `addPlugins()` triggers plugin setup before config is read. + stub.addPlugins.mockImplementation((next: any[]) => { + for (const plugin of next) { + if (typeof plugin?.setup === 'function') { + // Tests do not await `addPlugins`, so ensure `unwrapConfig` waits for setup. + pending.push(Promise.resolve(plugin.setup(stub))); + } + } + }); + + stub.unwrapConfig.mockImplementation(async () => { + await Promise.all(pending); + return mergedConfig; + }); + + return stub; }), }));