@@ -8,6 +8,7 @@ import * as vite from 'vite'
88import type { Plugin , ResolvedConfig } from 'vite'
99import {
1010 addRefreshWrapper ,
11+ avoidSourceMapOption ,
1112 getPreambleCode ,
1213 preambleCode ,
1314 runtimePublicPath ,
@@ -58,11 +59,6 @@ export interface Options {
5859 * reactRefreshHost: 'http://localhost:3000'
5960 */
6061 reactRefreshHost ?: string
61-
62- /**
63- * If set, disables the recommendation to use `@vitejs/plugin-react-oxc`
64- */
65- disableOxcRecommendation ?: boolean
6662}
6763
6864export type BabelOptions = Omit <
@@ -115,6 +111,8 @@ export default function viteReact(opts: Options = {}): Plugin[] {
115111 const jsxImportSource = opts . jsxImportSource ?? 'react'
116112 const jsxImportRuntime = `${ jsxImportSource } /jsx-runtime`
117113 const jsxImportDevRuntime = `${ jsxImportSource } /jsx-dev-runtime`
114+
115+ const isRolldownVite = 'rolldownVersion' in vite
118116 let runningInVite = false
119117 let isProduction = true
120118 let projectRoot = process . cwd ( )
@@ -133,37 +131,53 @@ export default function viteReact(opts: Options = {}): Plugin[] {
133131 const viteBabel : Plugin = {
134132 name : 'vite:react-babel' ,
135133 enforce : 'pre' ,
136- config ( ) {
137- if ( opts . jsxRuntime === 'classic' ) {
138- if ( 'rolldownVersion' in vite ) {
134+ config ( _userConfig , { command } ) {
135+ if ( 'rolldownVersion' in vite ) {
136+ if ( opts . jsxRuntime === 'classic' ) {
139137 return {
140138 oxc : {
141139 jsx : {
142140 runtime : 'classic' ,
141+ refresh : command === 'serve' ,
143142 // disable __self and __source injection even in dev
144143 // as this plugin injects them by babel and oxc will throw
145144 // if development is enabled and those properties are already present
146145 development : false ,
147146 } ,
147+ jsxRefreshInclude : include ,
148+ jsxRefreshExclude : exclude ,
148149 } ,
149150 }
150151 } else {
151152 return {
152- esbuild : {
153- jsx : 'transform' ,
153+ oxc : {
154+ jsx : {
155+ runtime : 'automatic' ,
156+ importSource : jsxImportSource ,
157+ refresh : command === 'serve' ,
158+ development : command === 'serve' ,
159+ } ,
160+ jsxRefreshInclude : include ,
161+ jsxRefreshExclude : exclude ,
154162 } ,
163+ optimizeDeps : { rollupOptions : { jsx : { mode : 'automatic' } } } ,
155164 }
156165 }
166+ }
167+
168+ if ( opts . jsxRuntime === 'classic' ) {
169+ return {
170+ esbuild : {
171+ jsx : 'transform' ,
172+ } ,
173+ }
157174 } else {
158175 return {
159176 esbuild : {
160177 jsx : 'automatic' ,
161- jsxImportSource : opts . jsxImportSource ,
178+ jsxImportSource : jsxImportSource ,
162179 } ,
163- optimizeDeps :
164- 'rolldownVersion' in vite
165- ? { rollupOptions : { jsx : { mode : 'automatic' } } }
166- : { esbuildOptions : { jsx : 'automatic' } } ,
180+ optimizeDeps : { esbuildOptions : { jsx : 'automatic' } } ,
167181 }
168182 }
169183 } ,
@@ -180,17 +194,6 @@ export default function viteReact(opts: Options = {}): Plugin[] {
180194 . map ( ( plugin ) => plugin . api ?. reactBabel )
181195 . filter ( defined )
182196
183- if (
184- 'rolldownVersion' in vite &&
185- ! opts . babel &&
186- ! hooks . length &&
187- ! opts . disableOxcRecommendation
188- ) {
189- config . logger . warn (
190- '[vite:react-babel] We recommend switching to `@vitejs/plugin-react-oxc` for improved performance. More information at https://vite.dev/rolldown' ,
191- )
192- }
193-
194197 if ( hooks . length > 0 ) {
195198 runPluginOverrides = ( babelOptions , context ) => {
196199 hooks . forEach ( ( hook ) => hook ( babelOptions , context , config ) )
@@ -252,7 +255,7 @@ export default function viteReact(opts: Options = {}): Plugin[] {
252255 ? importReactRE . test ( code )
253256 : code . includes ( jsxImportDevRuntime ) ||
254257 code . includes ( jsxImportRuntime ) ) )
255- if ( useFastRefresh ) {
258+ if ( useFastRefresh && ! isRolldownVite ) {
256259 plugins . push ( [
257260 await loadPlugin ( 'react-refresh/babel' ) ,
258261 { skipEnvCheck : true } ,
@@ -329,6 +332,59 @@ export default function viteReact(opts: Options = {}): Plugin[] {
329332 } ,
330333 }
331334
335+ const viteRefreshWrapper : Plugin = {
336+ name : 'vite:react:refresh-wrapper' ,
337+ apply : 'serve' ,
338+ transform : isRolldownVite
339+ ? {
340+ filter : {
341+ id : {
342+ include : makeIdFiltersToMatchWithQuery ( include ) ,
343+ exclude : makeIdFiltersToMatchWithQuery ( exclude ) ,
344+ } ,
345+ } ,
346+ handler ( code , id , options ) {
347+ const ssr = options ?. ssr === true
348+
349+ const [ filepath ] = id . split ( '?' )
350+ const isJSX = filepath . endsWith ( 'x' )
351+ const useFastRefresh =
352+ ! skipFastRefresh &&
353+ ! ssr &&
354+ ( isJSX ||
355+ code . includes ( jsxImportDevRuntime ) ||
356+ code . includes ( jsxImportRuntime ) )
357+ if ( ! useFastRefresh ) return
358+
359+ const { code : newCode } = addRefreshWrapper (
360+ code ,
361+ avoidSourceMapOption ,
362+ '@vitejs/plugin-react' ,
363+ id ,
364+ )
365+ return { code : newCode , map : null }
366+ } ,
367+ }
368+ : undefined ,
369+ }
370+
371+ const viteConfigPost : Plugin = {
372+ name : 'vite:react:config-post' ,
373+ enforce : 'post' ,
374+ config ( userConfig ) {
375+ if ( userConfig . server ?. hmr === false ) {
376+ return {
377+ oxc : {
378+ jsx : {
379+ refresh : false ,
380+ } ,
381+ } ,
382+ // oxc option is only available in rolldown-vite
383+ } as any
384+ }
385+ } ,
386+ }
387+
332388 const dependencies = [
333389 'react' ,
334390 'react-dom' ,
@@ -384,7 +440,7 @@ export default function viteReact(opts: Options = {}): Plugin[] {
384440 } ,
385441 }
386442
387- return [ viteBabel , viteReactRefresh ]
443+ return [ viteBabel , viteRefreshWrapper , viteConfigPost , viteReactRefresh ]
388444}
389445
390446viteReact . preambleCode = preambleCode
0 commit comments