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

fix(plugin-react): apply babel.plugins to project files only #5255

Merged
merged 5 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 44 additions & 30 deletions packages/plugin-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 + '/'))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a virtual module wants to avoid being transformed by Babel, it can include /node_modules/ in its module ID.


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([
Expand All @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A slight difference in behavior here:

  • Linked packages will now have their React.createElement calls reverse-compiled into JSX, so they will also use the automatic JSX runtime.

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' +
Expand All @@ -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(
Expand All @@ -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,
Expand Down
14 changes: 13 additions & 1 deletion packages/plugin-react/src/jsx-runtime/restore-jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@ type RestoredJSX = [result: t.File | null | undefined, isCommonJS: boolean]

let babelRestoreJSX: Promise<PluginItem> | 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<RestoredJSX> {
// 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'
Expand All @@ -24,7 +36,7 @@ export async function restoreJSX(
})

if (!hasCompiledJsx) {
return [null, false]
return jsxNotFound
}

// Support modules that use `import {Fragment} from 'react'`
Expand Down