From 377d0be5cf85a50240e160beaaafda77b7199452 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 9 Nov 2021 14:38:47 -0500 Subject: [PATCH] fix(plugin-react): apply `babel.plugins` to project files only (#5255) --- packages/plugin-react/src/index.ts | 74 +++++++++++-------- .../src/jsx-runtime/restore-jsx.ts | 14 +++- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index 2cc9eaab5a5f75..316149c8452287 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -108,36 +108,17 @@ export default function viteReact(opts: Options = {}): PluginOption[] { [] if (/\.(mjs|[tj]sx?)$/.test(extension)) { - const plugins = [...userPlugins] + const isJSX = extension.endsWith('x') + const isNodeModules = id.includes('/node_modules/') + const isProjectFile = + !isNodeModules && (id[0] === '\0' || id.startsWith(projectRoot + '/')) - const parserPlugins: typeof userParserPlugins = [ - ...userParserPlugins, - 'importMeta', - // This plugin is applied before esbuild transforms the code, - // so we need to enable some stage 3 syntax that is supported in - // TypeScript and some environments already. - 'topLevelAwait', - 'classProperties', - 'classPrivateProperties', - 'classPrivateMethods' - ] - - if (!extension.endsWith('.ts')) { - parserPlugins.push('jsx') - } - - const isTypeScript = /\.tsx?$/.test(extension) - if (isTypeScript) { - parserPlugins.push('typescript') - } - - const isNodeModules = id.includes('node_modules') + let plugins = isProjectFile ? [...userPlugins] : [] let useFastRefresh = false if (!skipFastRefresh && !ssr && !isNodeModules) { // Modules with .js or .ts extension must import React. - const isReactModule = - extension.endsWith('x') || code.includes('react') + const isReactModule = isJSX || code.includes('react') if (isReactModule && filter(id)) { useFastRefresh = true plugins.push([ @@ -148,16 +129,16 @@ export default function viteReact(opts: Options = {}): PluginOption[] { } let ast: t.File | null | undefined - if (isNodeModules || extension.endsWith('x')) { + if (!isProjectFile || isJSX) { if (useAutomaticRuntime) { // By reverse-compiling "React.createElement" calls into JSX, // React elements provided by dependencies will also use the // automatic runtime! - const [restoredAst, isCommonJS] = isNodeModules + const [restoredAst, isCommonJS] = !isProjectFile ? await restoreJSX(babel, code, id) : [null, false] - if (!isNodeModules || (ast = restoredAst)) { + if (isProjectFile || (ast = restoredAst)) { plugins.push([ await loadPlugin( '@babel/plugin-transform-react-jsx' + @@ -174,7 +155,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] { plugins.push(babelImportToRequire) } } - } else if (!isNodeModules) { + } else if (isProjectFile) { // These plugins are only needed for the classic runtime. if (!isProduction) { plugins.push( @@ -191,7 +172,40 @@ export default function viteReact(opts: Options = {}): PluginOption[] { } } - const isReasonReact = extension.endsWith('.bs.js') + // Plugins defined through this Vite plugin are only applied + // to modules within the project root, but "babel.config.js" + // files can define plugins that need to be applied to every + // module, including node_modules and linked packages. + const shouldSkip = + !plugins.length && + !opts.babel?.configFile && + !(isProjectFile && opts.babel?.babelrc) + + if (shouldSkip) { + return // Avoid parsing if no plugins exist. + } + + const parserPlugins: typeof userParserPlugins = [ + ...userParserPlugins, + 'importMeta', + // This plugin is applied before esbuild transforms the code, + // so we need to enable some stage 3 syntax that is supported in + // TypeScript and some environments already. + 'topLevelAwait', + 'classProperties', + 'classPrivateProperties', + 'classPrivateMethods' + ] + + if (!id.endsWith('.ts')) { + parserPlugins.push('jsx') + } + + if (/\.tsx?$/.test(id)) { + parserPlugins.push('typescript') + } + + const isReasonReact = id.endsWith('.bs.js') const babelOpts: TransformOptions = { babelrc: false, diff --git a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts index d246ec1cc15067..250fe43ce4d0c8 100644 --- a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts +++ b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts @@ -4,13 +4,25 @@ type RestoredJSX = [result: t.File | null | undefined, isCommonJS: boolean] let babelRestoreJSX: Promise | undefined +const jsxNotFound: RestoredJSX = [null, false] + /** Restore JSX from `React.createElement` calls */ export async function restoreJSX( babel: typeof import('@babel/core'), code: string, filename: string ): Promise { + // Avoid parsing the optimized react-dom since it will never + // contain compiled JSX and it's a pretty big file (800kb). + if (filename.includes('/.vite/react-dom.js')) { + return jsxNotFound + } + const [reactAlias, isCommonJS] = parseReactAlias(code) + if (!reactAlias) { + return jsxNotFound + } + const reactJsxRE = new RegExp( '\\b' + reactAlias + '\\.(createElement|Fragment)\\b', 'g' @@ -24,7 +36,7 @@ export async function restoreJSX( }) if (!hasCompiledJsx) { - return [null, false] + return jsxNotFound } // Support modules that use `import {Fragment} from 'react'`