From 5a6a705ca3a70a127d99df603449d2fab0339662 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 26 Oct 2021 17:11:28 -0400 Subject: [PATCH 1/8] feat(plugin-react): let Vite plugins define Babel plugins/presets --- packages/plugin-react/src/index.ts | 44 +++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index 5cbfc83c9bbbd5..b797ec0b02649c 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -13,6 +13,15 @@ import { import { babelImportToRequire } from './jsx-runtime/babel-import-to-require' import { restoreJSX } from './jsx-runtime/restore-jsx' +declare module 'vite' { + export interface Plugin { + /** + * Babel configuration applied in both dev and prod. + */ + babel?: Pick + } +} + export interface Options { include?: string | RegExp | Array exclude?: string | RegExp | Array @@ -54,7 +63,9 @@ export default function viteReact(opts: Options = {}): PluginOption[] { const useAutomaticRuntime = opts.jsxRuntime !== 'classic' - const userPlugins = opts.babel?.plugins || [] + let userPlugins = [...(opts.babel?.plugins || [])] + let userPresets = [...(opts.babel?.presets || [])] + const userParserPlugins = opts.parserPlugins || opts.babel?.parserOpts?.plugins || [] @@ -88,15 +99,33 @@ 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 isExtraneous = + plugin.name === 'react-refresh' || + (plugin !== viteReactJsx && plugin.name === 'vite:react-jsx') + + if (isExtraneous) + return config.logger.warn( `[@vitejs/plugin-react] You should stop using "${plugin.name}" ` + `since this plugin conflicts with it.` ) - ) + + if (plugin.babel) { + const { plugins, presets } = plugin.babel + if (plugins) { + userPlugins = + plugin.enforce === 'pre' + ? [...plugins, ...userPlugins] + : [...userPlugins, ...plugins] + } + if (presets) { + userPresets = + plugin.enforce === 'pre' + ? [...presets, ...userPresets] + : [...userPresets, ...presets] + } + } + }) }, async transform(code, id, options) { const ssr = typeof options === 'boolean' ? options : options?.ssr === true @@ -227,6 +256,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] { decoratorsBeforeExport: true }, plugins, + presets: userPresets, sourceMaps: true, // Vite handles sourcemap flattening inputSourceMap: false as any From 47ed56f9736472a634aa470f2c00b506cc487e4a Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 4 Jan 2022 12:20:36 -0500 Subject: [PATCH 2/8] Revert "feat(plugin-react): let Vite plugins define Babel plugins/presets" This reverts commit c655ef19d081199c4b323c19adfd01a8b3c10fe8. --- packages/plugin-react/src/index.ts | 44 +++++------------------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index b797ec0b02649c..5cbfc83c9bbbd5 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -13,15 +13,6 @@ import { import { babelImportToRequire } from './jsx-runtime/babel-import-to-require' import { restoreJSX } from './jsx-runtime/restore-jsx' -declare module 'vite' { - export interface Plugin { - /** - * Babel configuration applied in both dev and prod. - */ - babel?: Pick - } -} - export interface Options { include?: string | RegExp | Array exclude?: string | RegExp | Array @@ -63,9 +54,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] { const useAutomaticRuntime = opts.jsxRuntime !== 'classic' - let userPlugins = [...(opts.babel?.plugins || [])] - let userPresets = [...(opts.babel?.presets || [])] - + const userPlugins = opts.babel?.plugins || [] const userParserPlugins = opts.parserPlugins || opts.babel?.parserOpts?.plugins || [] @@ -99,33 +88,15 @@ export default function viteReact(opts: Options = {}): PluginOption[] { ) } - config.plugins.forEach((plugin) => { - const isExtraneous = - plugin.name === 'react-refresh' || - (plugin !== viteReactJsx && plugin.name === 'vite:react-jsx') - - if (isExtraneous) - return config.logger.warn( + config.plugins.forEach( + (plugin) => + (plugin.name === 'react-refresh' || + (plugin !== viteReactJsx && plugin.name === 'vite:react-jsx')) && + config.logger.warn( `[@vitejs/plugin-react] You should stop using "${plugin.name}" ` + `since this plugin conflicts with it.` ) - - if (plugin.babel) { - const { plugins, presets } = plugin.babel - if (plugins) { - userPlugins = - plugin.enforce === 'pre' - ? [...plugins, ...userPlugins] - : [...userPlugins, ...plugins] - } - if (presets) { - userPresets = - plugin.enforce === 'pre' - ? [...presets, ...userPresets] - : [...userPresets, ...presets] - } - } - }) + ) }, async transform(code, id, options) { const ssr = typeof options === 'boolean' ? options : options?.ssr === true @@ -256,7 +227,6 @@ export default function viteReact(opts: Options = {}): PluginOption[] { decoratorsBeforeExport: true }, plugins, - presets: userPresets, sourceMaps: true, // Vite handles sourcemap flattening inputSourceMap: false as any From dce43ac63663153dbf97222960b02d93238c238a Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 4 Jan 2022 12:40:23 -0500 Subject: [PATCH 3/8] feat(plugin-react): check for `api.reactBabel` on other plugins --- packages/plugin-react/src/index.ts | 84 ++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index 5cbfc83c9bbbd5..e15f29cdcbe6bb 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -2,7 +2,7 @@ import type { ParserOptions, TransformOptions, types as t } from '@babel/core' import * as babel from '@babel/core' import { createFilter } from '@rollup/pluginutils' import resolve from 'resolve' -import type { Plugin, PluginOption } from 'vite' +import type { Plugin, PluginOption, ResolvedConfig } from 'vite' import { addRefreshWrapper, isRefreshBoundary, @@ -43,6 +43,24 @@ export interface Options { parserPlugins?: ParserOptions['plugins'] } +type ReactBabelOptions = { + [P in keyof TransformOptions]-?: (P extends 'parserOpts' + ? { plugins: Exclude } + : unknown) & + Exclude +} + +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 = '/' @@ -54,11 +72,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'; @@ -88,15 +113,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 @@ -113,7 +144,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) { @@ -179,15 +210,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 @@ -206,35 +237,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! From 0791318c8f0f74df0dbd69b6226780773049656c Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 4 Jan 2022 13:35:06 -0500 Subject: [PATCH 4/8] fix: typescript error --- packages/plugin-react/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index e15f29cdcbe6bb..21a780785f694a 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -2,7 +2,7 @@ import type { ParserOptions, TransformOptions, types as t } from '@babel/core' import * as babel from '@babel/core' import { createFilter } from '@rollup/pluginutils' import resolve from 'resolve' -import type { Plugin, PluginOption, ResolvedConfig } from 'vite' +import type { Plugin, PluginOption } from 'vite' import { addRefreshWrapper, isRefreshBoundary, From 3a4cd874255b5fca5aa3529c96e5098a201100ec Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 4 Jan 2022 13:46:53 -0500 Subject: [PATCH 5/8] fix: remove non-customizable options from ReactBabelOptions type --- packages/plugin-react/src/index.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index 21a780785f694a..5289d0e507c5d3 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -43,11 +43,21 @@ export interface Options { parserPlugins?: ParserOptions['plugins'] } +type SupportedBabelOptions = Exclude< + keyof TransformOptions, + | 'ast' + | 'filename' + | 'root' + | 'sourceFileName' + | 'sourceMaps' + | 'inlineSourceMap' +> + type ReactBabelOptions = { - [P in keyof TransformOptions]-?: (P extends 'parserOpts' - ? { plugins: Exclude } - : unknown) & - Exclude + [P in SupportedBabelOptions]: Exclude & + (P extends 'parserOpts' + ? { plugins: Exclude } + : unknown) } declare module 'vite' { From 3303fcc6772b67bb6643b4a0cb1d85b2b4167a46 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 4 Jan 2022 13:48:25 -0500 Subject: [PATCH 6/8] fix: remove non-customizable options from `babel` option --- packages/plugin-react/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index 5289d0e507c5d3..3b74854dc590ed 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -36,7 +36,7 @@ export interface Options { /** * Babel configuration applied in both dev and prod. */ - babel?: TransformOptions + babel?: Pick /** * @deprecated Use `babel.parserOpts.plugins` instead */ From 97ad93b94917c702401bb6be4add26dd6924b263 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 4 Jan 2022 13:49:23 -0500 Subject: [PATCH 7/8] chore: fix typo --- packages/plugin-react/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index 3b74854dc590ed..f6e5bd81fe46e1 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -50,7 +50,7 @@ type SupportedBabelOptions = Exclude< | 'root' | 'sourceFileName' | 'sourceMaps' - | 'inlineSourceMap' + | 'inputSourceMap' > type ReactBabelOptions = { From 53399ab748f5eebd2debc628ea1763a02650847a Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 4 Jan 2022 14:20:39 -0500 Subject: [PATCH 8/8] fix: typescript error in production build --- packages/plugin-react/src/index.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index f6e5bd81fe46e1..aa6ae8197ff036 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -36,15 +36,15 @@ export interface Options { /** * Babel configuration applied in both dev and prod. */ - babel?: Pick + babel?: BabelOptions /** * @deprecated Use `babel.parserOpts.plugins` instead */ parserPlugins?: ParserOptions['plugins'] } -type SupportedBabelOptions = Exclude< - keyof TransformOptions, +export type BabelOptions = Omit< + TransformOptions, | 'ast' | 'filename' | 'root' @@ -53,11 +53,16 @@ type SupportedBabelOptions = Exclude< | 'inputSourceMap' > -type ReactBabelOptions = { - [P in SupportedBabelOptions]: Exclude & - (P extends 'parserOpts' - ? { plugins: Exclude } - : unknown) +/** + * The object type used by the `options` passed to plugins with + * an `api.reactBabel` method. + */ +export interface ReactBabelOptions extends BabelOptions { + plugins: Extract + presets: Extract + parserOpts: ParserOptions & { + plugins: Extract + } } declare module 'vite' {