Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin-react): check for api.reactBabel on other plugins #5454

Merged
merged 8 commits into from
Jan 4, 2022
99 changes: 71 additions & 28 deletions packages/plugin-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,46 @@ export interface Options {
/**
* Babel configuration applied in both dev and prod.
*/
babel?: TransformOptions
babel?: BabelOptions
/**
* @deprecated Use `babel.parserOpts.plugins` instead
*/
parserPlugins?: ParserOptions['plugins']
}

export type BabelOptions = Omit<
TransformOptions,
| 'ast'
| 'filename'
| 'root'
| 'sourceFileName'
| 'sourceMaps'
| 'inputSourceMap'
>

/**
* The object type used by the `options` passed to plugins with
* an `api.reactBabel` method.
*/
export interface ReactBabelOptions extends BabelOptions {
plugins: Extract<BabelOptions['plugins'], any[]>
presets: Extract<BabelOptions['presets'], any[]>
parserOpts: ParserOptions & {
plugins: Extract<ParserOptions['plugins'], any[]>
}
}

declare module 'vite' {
export interface Plugin {
api?: {
/**
* Manipulate the Babel options of `@vitejs/plugin-react`
*/
reactBabel?: (options: ReactBabelOptions, config: ResolvedConfig) => void
}
}
}

export default function viteReact(opts: Options = {}): PluginOption[] {
// Provide default values for Rollup compat.
let base = '/'
Expand All @@ -54,11 +87,18 @@ export default function viteReact(opts: Options = {}): PluginOption[] {

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

const userPlugins = opts.babel?.plugins || []
const userParserPlugins =
opts.parserPlugins || opts.babel?.parserOpts?.plugins || []
const babelOptions = {
babelrc: false,
configFile: false,
...opts.babel
} as ReactBabelOptions

// Support pattens like:
babelOptions.plugins ||= []
babelOptions.presets ||= []
babelOptions.parserOpts ||= {} as any
babelOptions.parserOpts.plugins ||= opts.parserPlugins || []

// Support patterns like:
// - import * as React from 'react';
// - import React from 'react';
// - import React, {useEffect} from 'react';
Expand Down Expand Up @@ -88,15 +128,21 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
)
}

config.plugins.forEach(
(plugin) =>
(plugin.name === 'react-refresh' ||
(plugin !== viteReactJsx && plugin.name === 'vite:react-jsx')) &&
config.logger.warn(
config.plugins.forEach((plugin) => {
const hasConflict =
plugin.name === 'react-refresh' ||
(plugin !== viteReactJsx && plugin.name === 'vite:react-jsx')

if (hasConflict)
return config.logger.warn(
`[@vitejs/plugin-react] You should stop using "${plugin.name}" ` +
`since this plugin conflicts with it.`
)
)

if (plugin.api?.reactBabel) {
plugin.api.reactBabel(babelOptions, config)
}
})
},
async transform(code, id, options) {
const ssr = typeof options === 'boolean' ? options : options?.ssr === true
Expand All @@ -113,7 +159,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
const isProjectFile =
!isNodeModules && (id[0] === '\0' || id.startsWith(projectRoot + '/'))

const plugins = isProjectFile ? [...userPlugins] : []
const plugins = isProjectFile ? [...babelOptions.plugins] : []

let useFastRefresh = false
if (!skipFastRefresh && !ssr && !isNodeModules) {
Expand Down Expand Up @@ -179,15 +225,15 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
// module, including node_modules and linked packages.
const shouldSkip =
!plugins.length &&
!opts.babel?.configFile &&
!(isProjectFile && opts.babel?.babelrc)
!babelOptions.configFile &&
!(isProjectFile && babelOptions.babelrc)

if (shouldSkip) {
return // Avoid parsing if no plugins exist.
}

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

const isReasonReact = extension.endsWith('.bs.js')
const transformAsync = ast
? babel.transformFromAstAsync.bind(babel, ast, code)
: babel.transformAsync.bind(babel, code)

const babelOpts: TransformOptions = {
babelrc: false,
configFile: false,
...opts.babel,
const isReasonReact = extension.endsWith('.bs.js')
const result = await transformAsync({
...babelOptions,
ast: !isReasonReact,
root: projectRoot,
filename: id,
sourceFileName: filepath,
parserOpts: {
...opts.babel?.parserOpts,
...babelOptions.parserOpts,
sourceType: 'module',
allowAwaitOutsideFunction: true,
plugins: parserPlugins
},
generatorOpts: {
...opts.babel?.generatorOpts,
...babelOptions.generatorOpts,
decoratorsBeforeExport: true
},
plugins,
sourceMaps: true,
// Vite handles sourcemap flattening
inputSourceMap: false as any
}

const result = ast
? await babel.transformFromAstAsync(ast, code, babelOpts)
: await babel.transformAsync(code, babelOpts)
})

if (result) {
let code = result.code!
Expand Down