diff --git a/.changeset/rspack-hmr-for-ssr-dev.md b/.changeset/rspack-hmr-for-ssr-dev.md new file mode 100644 index 0000000..6efc506 --- /dev/null +++ b/.changeset/rspack-hmr-for-ssr-dev.md @@ -0,0 +1,5 @@ +--- +"rsbuild-plugin-react-router": patch +--- + +Enable Rspack HMR for ESM server outputs 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: diff --git a/tests/features.test.ts b/tests/features.test.ts index 9a5afe3..ad1e3bf 100644 --- a/tests/features.test.ts +++ b/tests/features.test.ts @@ -12,7 +12,8 @@ describe('pluginReactRouter', () => { rsbuild.addPlugins([pluginReactRouter()]); const config = await rsbuild.unwrapConfig(); - expect(config.dev.hmr).toBe(false); + // 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 d8288b6..47c3e4b 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -11,7 +11,8 @@ describe('pluginReactRouter', () => { rsbuild.addPlugins([pluginReactRouter()]); const config = await rsbuild.unwrapConfig(); - expect(config.dev.hmr).toBe(false); + // 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; }), }));