From 7ef4b76b3f5e33deae33c2c376bb3e1e2dd85834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Sun, 18 Oct 2026 20:01:31 +0200 Subject: [PATCH] feat(plugin-react): Add refreshInclude and refreshExclude options --- packages/plugin-react/CHANGELOG.md | 4 +++ packages/plugin-react/src/index.ts | 48 +++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/plugin-react/CHANGELOG.md b/packages/plugin-react/CHANGELOG.md index 5e7f89292..b42eb8e63 100644 --- a/packages/plugin-react/CHANGELOG.md +++ b/packages/plugin-react/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Add `refreshInclude` and `refreshExclude` options ([#1516](https://github.com/vitejs/vite-plugin-react/pull/1516)) + +Add `refreshInclude` and `refreshExclude` options to skip fast refresh for certain files that won't run in the browser. + ## 6.1.2 (2026-10-05) ### Fix HMR for compound components ([#1484](https://github.com/vitejs/vite-plugin-react/pull/1484)) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index dea10d566..9a0ad45d1 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -12,7 +12,7 @@ import { silenceUseClientWarning, virtualPreamblePlugin, } from '@vitejs/react-common' -import type { Plugin, ServerOptions } from 'vite' +import { createFilter, type Plugin, type ServerOptions } from 'vite' import { reactRefreshWrapperPlugin } from 'vite/internal' import type { ReactCompilerOptions } from '#optionalTypes' import { defaultCodeFilter, reactCompilerPreset } from './reactCompilerPreset' @@ -44,6 +44,16 @@ export interface Options { * @default /\/node_modules\// */ exclude?: string | RegExp | Array + /** + * Can be used to skip fast refresh for certain files that won't run in the browser. + * @example options.include + */ + refreshInclude?: string | RegExp | Array + /** + * Can be used to skip fast refresh for certain files that won't run in the browser. + * @default options.exclude + */ + refreshExclude?: string | RegExp | Array /** * Control where the JSX factory is imported from. * https://oxc.rs/docs/guide/usage/transformer/jsx.html#import-source @@ -78,6 +88,12 @@ const defaultExcludeRE = /\/node_modules\// function viteReact(opts: Options = {}): Plugin[] { const include = opts.include ?? defaultIncludeRE const exclude = opts.exclude ?? defaultExcludeRE + const jsxRefreshInclude = makeIdFiltersToMatchWithQuery( + opts.refreshInclude ?? include, + ) + const jsxRefreshExclude = makeIdFiltersToMatchWithQuery( + opts.refreshExclude ?? exclude, + ) const jsxImportSource = opts.jsxImportSource ?? 'react' const jsxImportRuntime = `${jsxImportSource}/jsx-runtime` @@ -108,8 +124,8 @@ function viteReact(opts: Options = {}): Plugin[] { runtime: 'classic', refresh, }, - jsxRefreshInclude: makeIdFiltersToMatchWithQuery(include), - jsxRefreshExclude: makeIdFiltersToMatchWithQuery(exclude), + jsxRefreshInclude, + jsxRefreshExclude, }, } } else { @@ -120,8 +136,8 @@ function viteReact(opts: Options = {}): Plugin[] { importSource: opts.jsxImportSource, refresh, }, - jsxRefreshInclude: makeIdFiltersToMatchWithQuery(include), - jsxRefreshExclude: makeIdFiltersToMatchWithQuery(exclude), + jsxRefreshInclude, + jsxRefreshExclude, }, optimizeDeps: { rolldownOptions: { transform: { jsx: { runtime: 'automatic' } } }, @@ -170,8 +186,8 @@ function viteReact(opts: Options = {}): Plugin[] { return reactRefreshWrapperPlugin({ cwd: process.cwd(), - include: makeIdFiltersToMatchWithQuery(include), - exclude: makeIdFiltersToMatchWithQuery(exclude), + include: jsxRefreshInclude, + exclude: jsxRefreshExclude, jsxImportSource, reactRefreshHost: opts.reactRefreshHost ?? '', }) as unknown as boolean @@ -301,7 +317,10 @@ function createReactCompilerPlugin( { logDiagnostics, ...reactCompilerOptions }: ReactCompilerPluginOptions, include: NonNullable, exclude: NonNullable, - reactOptions: Pick, + reactOptions: Pick< + Options, + 'jsxRuntime' | 'jsxImportSource' | 'refreshInclude' | 'refreshExclude' + >, isFastRefreshEnabled: () => boolean, ): Plugin { let jsxDevelopment = false @@ -313,6 +332,10 @@ function createReactCompilerPlugin( const jsxImportSource = reactOptions.jsxImportSource ?? 'react' const jsxImportRuntime = `${jsxImportSource}/jsx-runtime` const jsxImportDevRuntime = `${jsxImportSource}/jsx-dev-runtime` + const jsxRefreshFilter = + reactOptions.refreshInclude || reactOptions.refreshExclude + ? createFilter(reactOptions.refreshInclude, reactOptions.refreshExclude) + : undefined const loadCompiler = async ( onError: (message: string) => never, @@ -359,9 +382,12 @@ function createReactCompilerPlugin( const refreshEnabled = isClient && isFastRefreshEnabled() && - (isJSX || - code.includes(jsxImportRuntime) || - code.includes(jsxImportDevRuntime)) + ((jsxRefreshFilter && !jsxRefreshFilter(id)) || + !( + isJSX || + code.includes(jsxImportRuntime) || + code.includes(jsxImportDevRuntime) + )) const shouldCompile = isClient &&