Skip to content

feat: add fastRefresh option to disable Fast Refresh - #1467

Open
kklem0 wants to merge 1 commit into
vitejs:mainfrom
kklem0:feat/fast-refresh-option
Open

kklem0 wants to merge 1 commit into
vitejs:mainfrom
kklem0:feat/fast-refresh-option

Conversation

@kklem0

@kklem0 kklem0 commented Sep 21, 2026

Copy link
Copy Markdown

Description

Alternative to #1436, following @ArnaudBarre's suggestion in #1436 (comment): instead of exporting a second plugin, add an option to react() that turns Fast Refresh off, so the plugin can run next to another plugin that already provides it. The motivating case is React Router framework mode: reactRouter() runs react-refresh/babel itself and wraps modules with its own runtime, so adding react() today fails with Identifier 'RefreshRuntime' has already been declared (remix-run/react-router#12870), which leaves no way to use the Rust React Compiler in those apps.

import { reactRouter } from '@react-router/dev/vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react({ compiler: true, fastRefresh: false }), reactRouter()],
})

Behavior:

  • fastRefresh: false folds into the existing skipFastRefresh logic (the same path as production, build and server.hmr: false), so the oxc jsx.refresh transform, the refresh wrapper, the preamble and the compiler plugin's refresh flag are all skipped. HMR itself stays on.
  • JSX, TypeScript and the React Compiler stay in this plugin. With compiler, vite:react-compiler is enforce: 'pre', so it sees route modules before React Router rewrites them regardless of plugin order, and JSX is lowered in the same oxc-transform-react pass as the compiler, so jsxDEV source positions match the source file (the jsx: 'preserve' concern from feat: add standalone reactCompiler plugin #1436 does not apply).
  • fastRefresh existed until 3.x and was removed in 4.0 (feat!: allow to run Babel on non js/ts extensions #122) because the plugin could always enable it on its own. This brings it back for the case where a different plugin owns it.

Verified against a React Router 8.4 framework-mode app (SSR) with react({ compiler: true, fastRefresh: false }) next to reactRouter():

  • react-router build succeeds; the client bundle contains the compiled output and no refresh code.
  • In dev, the transformed route module is compiled, has JSX lowered to jsxDEV, imports RefreshRuntime exactly once from React Router's virtual HMR runtime and does not reference /@react-refresh. The HTML has no preamble.
  • jsxDEV lineNumber values match the JSX lines of the source file.
  • Editing the route module with the counter at 2 yields count is 2!: React Router's Fast Refresh preserves state on the compiled output, with no browser or server errors.

Included:

  • README section for the option, a pointer from the Rust React Compiler section, and a changelog entry.
  • Unit tests for the option (refresh transform config, wrapper, preamble, compiler transform output).
  • playground/fast-refresh-false with react({ compiler: true, fastRefresh: false }), asserting compiled output in serve and build, no refresh code or preamble, and a full reload on edit since that playground has no Fast Refresh boundary.

Happy to rename the option if fastRefresh is not the preferred name.

🤖 Generated with Claude Code

Allows `react()` to run next to a plugin that already provides Fast
Refresh, such as React Router in framework mode, so that JSX and the
React Compiler can still be handled by this plugin.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
@ArnaudBarre

Copy link
Copy Markdown
Member

Can you update the added playground to use react-router and remove the unit tests?
Then I will check it out but the amount of changes in the source code is really low so I'm ok with going forward with this

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants