diff --git a/packages/common/refresh-utils.ts b/packages/common/refresh-utils.ts index 008828797..559cbaa04 100644 --- a/packages/common/refresh-utils.ts +++ b/packages/common/refresh-utils.ts @@ -21,6 +21,7 @@ export function addRefreshWrapper( map: M | string | typeof avoidSourceMapOption, pluginName: string, id: string, + reactRefreshHost = '', ): { code: string; map: M | null | string } { const hasRefresh = refreshContentRE.test(code) const onlyReactComp = !hasRefresh && reactCompRE.test(code) @@ -70,7 +71,7 @@ if (import.meta.hot && !inWebWorker) { } const sharedHead = removeLineBreaksIfNeeded( - `import * as RefreshRuntime from "${runtimePublicPath}"; + `import * as RefreshRuntime from "${reactRefreshHost}${runtimePublicPath}"; const inWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; `, diff --git a/packages/plugin-react-swc/CHANGELOG.md b/packages/plugin-react-swc/CHANGELOG.md index 08c8c3da2..e58b5f9f4 100644 --- a/packages/plugin-react-swc/CHANGELOG.md +++ b/packages/plugin-react-swc/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +### Add `reactRefreshHost` option + +Add `reactRefreshHost` option to set a React Fast Refresh runtime URL prefix. +This is useful in a module federation context to enable HMR by specifying the host application URL in the Vite config of a remote application. +See full discussion here: https://github.com/module-federation/vite/issues/183#issuecomment-2751825367 + +```ts +export default defineConfig({ + plugins: [react({ reactRefreshHost: 'http://localhost:3000' })], +}) +``` + ## 3.9.0-beta.2 (2025-04-09) ## 3.9.0-beta.0 (2025-04-09) diff --git a/packages/plugin-react-swc/README.md b/packages/plugin-react-swc/README.md index b00ac0beb..76bd52e6f 100644 --- a/packages/plugin-react-swc/README.md +++ b/packages/plugin-react-swc/README.md @@ -93,6 +93,16 @@ react({ }) ``` +### reactRefreshHost + +The `reactRefreshHost` option is only necessary in a module federation context. It enables HMR to work between a remote & host application. In your remote Vite config, you would add your host origin: + +```js +react({ reactRefreshHost: 'http://localhost:3000' }) +``` + +Under the hood, this simply updates the React Fash Refresh runtime URL from `/@react-refresh` to `http://localhost:3000/@react-refresh` to ensure there is only one Refresh runtime across the whole application. Note that if you define `base` option in the host application, you need to include it in the option, like: `http://localhost:3000/{base}`. + ### useAtYourOwnRisk_mutateSwcOptions The future of Vite is with OXC, and from the beginning this was a design choice to not exposed too many specialties from SWC so that Vite React users can move to another transformer later. diff --git a/packages/plugin-react-swc/src/index.ts b/packages/plugin-react-swc/src/index.ts index 52f211f4c..9c33c2248 100644 --- a/packages/plugin-react-swc/src/index.ts +++ b/packages/plugin-react-swc/src/index.ts @@ -58,6 +58,14 @@ type Options = { * Exclusion of node_modules should be handled by the function if needed. */ parserConfig?: (id: string) => ParserConfig | undefined + /** + * React Fast Refresh runtime URL prefix. + * Useful in a module federation context to enable HMR by specifying + * the host application URL in a Vite config of a remote application. + * @example + * reactRefreshHost: 'http://localhost:3000' + */ + reactRefreshHost?: string /** * The future of Vite is with OXC, and from the beginning this was a design choice * to not exposed too many specialties from SWC so that Vite React users can move to @@ -78,6 +86,7 @@ const react = (_options?: Options): PluginOption[] => { : undefined, devTarget: _options?.devTarget ?? 'es2020', parserConfig: _options?.parserConfig, + reactRefreshHost: _options?.reactRefreshHost, useAtYourOwnRisk_mutateSwcOptions: _options?.useAtYourOwnRisk_mutateSwcOptions, } @@ -152,6 +161,7 @@ const react = (_options?: Options): PluginOption[] => { result.map!, '@vitejs/plugin-react-swc', id, + options.reactRefreshHost, ) }, }, diff --git a/packages/plugin-react/CHANGELOG.md b/packages/plugin-react/CHANGELOG.md index 928a751ce..90a1d8b7c 100644 --- a/packages/plugin-react/CHANGELOG.md +++ b/packages/plugin-react/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +### Add `reactRefreshHost` option + +Add `reactRefreshHost` option to set a React Fast Refresh runtime URL prefix. +This is useful in a module federation context to enable HMR by specifying the host application URL in the Vite config of a remote application. +See full discussion here: https://github.com/module-federation/vite/issues/183#issuecomment-2751825367 + +```ts +export default defineConfig({ + plugins: [react({ reactRefreshHost: 'http://localhost:3000' })], +}) +``` + ## 4.4.0-beta.1 (2025-04-09) ## 4.4.0-beta.0 (2025-04-09) diff --git a/packages/plugin-react/README.md b/packages/plugin-react/README.md index 27683dd4d..7eaa29ab4 100644 --- a/packages/plugin-react/README.md +++ b/packages/plugin-react/README.md @@ -94,6 +94,16 @@ This option does not enable _code transformation_. That is handled by esbuild. Here's the [complete list of Babel parser plugins](https://babeljs.io/docs/en/babel-parser#ecmascript-proposalshttpsgithubcombabelproposals). +### reactRefreshHost + +The `reactRefreshHost` option is only necessary in a module federation context. It enables HMR to work between a remote & host application. In your remote Vite config, you would add your host origin: + +```js +react({ reactRefreshHost: 'http://localhost:3000' }) +``` + +Under the hood, this simply updates the React Fash Refresh runtime URL from `/@react-refresh` to `http://localhost:3000/@react-refresh` to ensure there is only one Refresh runtime across the whole application. Note that if you define `base` option in the host application, you need to include it in the option, like: `http://localhost:3000/{base}`. + ## Middleware mode In [middleware mode](https://vite.dev/config/server-options.html#server-middlewaremode), you should make sure your entry `index.html` file is transformed by Vite. Here's an example for an Express server: diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index 26739e56a..0923207a0 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -49,6 +49,14 @@ export interface Options { babel?: | BabelOptions | ((id: string, options: { ssr?: boolean }) => BabelOptions) + /** + * React Fast Refresh runtime URL prefix. + * Useful in a module federation context to enable HMR by specifying + * the host application URL in the Vite config of a remote application. + * @example + * reactRefreshHost: 'http://localhost:3000' + */ + reactRefreshHost?: string } export type BabelOptions = Omit< @@ -263,6 +271,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] { result.map!, '@vitejs/plugin-react', id, + opts.reactRefreshHost, ) } },