Skip to content

Commit abecbc2

Browse files
committed
feat: use OXC react refresh transform in plugin-react when using rolldown-vite
1 parent 934a240 commit abecbc2

1 file changed

Lines changed: 84 additions & 28 deletions

File tree

packages/plugin-react/src/index.ts

Lines changed: 84 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as vite from 'vite'
88
import type { Plugin, ResolvedConfig } from 'vite'
99
import {
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

6864
export 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

390446
viteReact.preambleCode = preambleCode

0 commit comments

Comments
 (0)