Skip to content

Commit 2ab41b3

Browse files
authored
feat(plugin-react): check for api.reactBabel on other plugins (#5454)
1 parent bd9e97b commit 2ab41b3

1 file changed

Lines changed: 71 additions & 28 deletions

File tree

packages/plugin-react/src/index.ts

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,46 @@ export interface Options {
3636
/**
3737
* Babel configuration applied in both dev and prod.
3838
*/
39-
babel?: TransformOptions
39+
babel?: BabelOptions
4040
/**
4141
* @deprecated Use `babel.parserOpts.plugins` instead
4242
*/
4343
parserPlugins?: ParserOptions['plugins']
4444
}
4545

46+
export type BabelOptions = Omit<
47+
TransformOptions,
48+
| 'ast'
49+
| 'filename'
50+
| 'root'
51+
| 'sourceFileName'
52+
| 'sourceMaps'
53+
| 'inputSourceMap'
54+
>
55+
56+
/**
57+
* The object type used by the `options` passed to plugins with
58+
* an `api.reactBabel` method.
59+
*/
60+
export interface ReactBabelOptions extends BabelOptions {
61+
plugins: Extract<BabelOptions['plugins'], any[]>
62+
presets: Extract<BabelOptions['presets'], any[]>
63+
parserOpts: ParserOptions & {
64+
plugins: Extract<ParserOptions['plugins'], any[]>
65+
}
66+
}
67+
68+
declare module 'vite' {
69+
export interface Plugin {
70+
api?: {
71+
/**
72+
* Manipulate the Babel options of `@vitejs/plugin-react`
73+
*/
74+
reactBabel?: (options: ReactBabelOptions, config: ResolvedConfig) => void
75+
}
76+
}
77+
}
78+
4679
export default function viteReact(opts: Options = {}): PluginOption[] {
4780
// Provide default values for Rollup compat.
4881
let base = '/'
@@ -54,11 +87,18 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
5487

5588
const useAutomaticRuntime = opts.jsxRuntime !== 'classic'
5689

57-
const userPlugins = opts.babel?.plugins || []
58-
const userParserPlugins =
59-
opts.parserPlugins || opts.babel?.parserOpts?.plugins || []
90+
const babelOptions = {
91+
babelrc: false,
92+
configFile: false,
93+
...opts.babel
94+
} as ReactBabelOptions
6095

61-
// Support pattens like:
96+
babelOptions.plugins ||= []
97+
babelOptions.presets ||= []
98+
babelOptions.parserOpts ||= {} as any
99+
babelOptions.parserOpts.plugins ||= opts.parserPlugins || []
100+
101+
// Support patterns like:
62102
// - import * as React from 'react';
63103
// - import React from 'react';
64104
// - import React, {useEffect} from 'react';
@@ -88,15 +128,21 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
88128
)
89129
}
90130

91-
config.plugins.forEach(
92-
(plugin) =>
93-
(plugin.name === 'react-refresh' ||
94-
(plugin !== viteReactJsx && plugin.name === 'vite:react-jsx')) &&
95-
config.logger.warn(
131+
config.plugins.forEach((plugin) => {
132+
const hasConflict =
133+
plugin.name === 'react-refresh' ||
134+
(plugin !== viteReactJsx && plugin.name === 'vite:react-jsx')
135+
136+
if (hasConflict)
137+
return config.logger.warn(
96138
`[@vitejs/plugin-react] You should stop using "${plugin.name}" ` +
97139
`since this plugin conflicts with it.`
98140
)
99-
)
141+
142+
if (plugin.api?.reactBabel) {
143+
plugin.api.reactBabel(babelOptions, config)
144+
}
145+
})
100146
},
101147
async transform(code, id, options) {
102148
const ssr = typeof options === 'boolean' ? options : options?.ssr === true
@@ -113,7 +159,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
113159
const isProjectFile =
114160
!isNodeModules && (id[0] === '\0' || id.startsWith(projectRoot + '/'))
115161

116-
const plugins = isProjectFile ? [...userPlugins] : []
162+
const plugins = isProjectFile ? [...babelOptions.plugins] : []
117163

118164
let useFastRefresh = false
119165
if (!skipFastRefresh && !ssr && !isNodeModules) {
@@ -179,15 +225,15 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
179225
// module, including node_modules and linked packages.
180226
const shouldSkip =
181227
!plugins.length &&
182-
!opts.babel?.configFile &&
183-
!(isProjectFile && opts.babel?.babelrc)
228+
!babelOptions.configFile &&
229+
!(isProjectFile && babelOptions.babelrc)
184230

185231
if (shouldSkip) {
186232
return // Avoid parsing if no plugins exist.
187233
}
188234

189-
const parserPlugins: typeof userParserPlugins = [
190-
...userParserPlugins,
235+
const parserPlugins: typeof babelOptions.parserOpts.plugins = [
236+
...babelOptions.parserOpts.plugins,
191237
'importMeta',
192238
// This plugin is applied before esbuild transforms the code,
193239
// so we need to enable some stage 3 syntax that is supported in
@@ -206,35 +252,32 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
206252
parserPlugins.push('typescript')
207253
}
208254

209-
const isReasonReact = extension.endsWith('.bs.js')
255+
const transformAsync = ast
256+
? babel.transformFromAstAsync.bind(babel, ast, code)
257+
: babel.transformAsync.bind(babel, code)
210258

211-
const babelOpts: TransformOptions = {
212-
babelrc: false,
213-
configFile: false,
214-
...opts.babel,
259+
const isReasonReact = extension.endsWith('.bs.js')
260+
const result = await transformAsync({
261+
...babelOptions,
215262
ast: !isReasonReact,
216263
root: projectRoot,
217264
filename: id,
218265
sourceFileName: filepath,
219266
parserOpts: {
220-
...opts.babel?.parserOpts,
267+
...babelOptions.parserOpts,
221268
sourceType: 'module',
222269
allowAwaitOutsideFunction: true,
223270
plugins: parserPlugins
224271
},
225272
generatorOpts: {
226-
...opts.babel?.generatorOpts,
273+
...babelOptions.generatorOpts,
227274
decoratorsBeforeExport: true
228275
},
229276
plugins,
230277
sourceMaps: true,
231278
// Vite handles sourcemap flattening
232279
inputSourceMap: false as any
233-
}
234-
235-
const result = ast
236-
? await babel.transformFromAstAsync(ast, code, babelOpts)
237-
: await babel.transformAsync(code, babelOpts)
280+
})
238281

239282
if (result) {
240283
let code = result.code!

0 commit comments

Comments
 (0)