diff --git a/packages/plugin-react/src/jsx-runtime/babel-restore-jsx.ts b/packages/plugin-react/src/jsx-runtime/babel-restore-jsx.ts index 669a0aeeced207..91b4db5411bf26 100644 --- a/packages/plugin-react/src/jsx-runtime/babel-restore-jsx.ts +++ b/packages/plugin-react/src/jsx-runtime/babel-restore-jsx.ts @@ -4,6 +4,10 @@ */ import type * as babel from '@babel/core' +interface PluginOptions { + reactAlias: string +} + /** * Visitor factory for babel, converting React.createElement(...) to ... * @@ -17,7 +21,10 @@ import type * as babel from '@babel/core' * * Any of those arguments might also be missing (undefined) and/or invalid. */ -export default function ({ types: t }: typeof babel): babel.PluginObj { +export default function ( + { types: t }: typeof babel, + { reactAlias = 'React' }: PluginOptions +): babel.PluginObj { /** * Get a `JSXElement` from a `CallExpression`. * Returns `null` if this impossible. @@ -48,7 +55,7 @@ export default function ({ types: t }: typeof babel): babel.PluginObj { if ( t.isJSXMemberExpression(name) && t.isJSXIdentifier(name.object) && - name.object.name === 'React' && + name.object.name === reactAlias && name.property.name === 'Fragment' ) { return t.jsxFragment( @@ -182,7 +189,7 @@ export default function ({ types: t }: typeof babel): babel.PluginObj { const isReactCreateElement = (node: any) => t.isCallExpression(node) && t.isMemberExpression(node.callee) && - t.isIdentifier(node.callee.object, { name: 'React' }) && + t.isIdentifier(node.callee.object, { name: reactAlias }) && t.isIdentifier(node.callee.property, { name: 'createElement' }) && !node.callee.computed diff --git a/packages/plugin-react/src/jsx-runtime/restore-jsx.spec.ts b/packages/plugin-react/src/jsx-runtime/restore-jsx.spec.ts index 7d5b14bfc9cfd4..00ea39673ec415 100644 --- a/packages/plugin-react/src/jsx-runtime/restore-jsx.spec.ts +++ b/packages/plugin-react/src/jsx-runtime/restore-jsx.spec.ts @@ -81,7 +81,10 @@ describe('restore-jsx', () => { expect( await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; React__default.createElement(foo)`) - ).toBeNull() + ).toMatchInlineSnapshot(` + "import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; + React__default.createElement(foo);" + `) expect( await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; React__default.createElement("h1")`) @@ -104,7 +107,12 @@ describe('restore-jsx', () => { expect( await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; React__default.createElement(foo, {hi: there})`) - ).toBeNull() + ).toMatchInlineSnapshot(` + "import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; + React__default.createElement(foo, { + hi: there + });" + `) expect( await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; React__default.createElement("h1", {hi: there})`) @@ -114,4 +122,26 @@ describe('restore-jsx', () => { React__default.createElement(Foo, {hi: there})`) ).toMatch(`;`) }) + + it('should handle Fragment', async () => { + expect( + await jsx(`import R, { Fragment } from 'react'; + R.createElement(Fragment) + `) + ).toMatchInlineSnapshot(` + "import R, { Fragment } from 'react'; + ;" + `) + }) + + it('should handle Fragment alias', async () => { + expect( + await jsx(`import RA, { Fragment as F } from 'react'; + RA.createElement(F, null, RA.createElement(RA.Fragment)) + `) + ).toMatchInlineSnapshot(` + "import RA, { Fragment as F } from 'react'; + <>;" + `) + }) }) diff --git a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts index d67a54e8f08aad..ddc3c1530e0d9f 100644 --- a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts +++ b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts @@ -33,34 +33,12 @@ export async function restoreJSX( return jsxNotFound } - let hasCompiledJsx = false - - const fragmentPattern = `\\b${reactAlias}\\.Fragment\\b` - const createElementPattern = `\\b${reactAlias}\\.createElement\\(\\s*([A-Z"'][\\w$.]*["']?)` - - // Replace the alias with "React" so JSX can be reverse compiled. - code = code - .replace(new RegExp(fragmentPattern, 'g'), () => { - hasCompiledJsx = true - return 'React.Fragment' - }) - .replace(new RegExp(createElementPattern, 'g'), (original, component) => { - if (/^[a-z][\w$]*$/.test(component)) { - // Take care not to replace the alias for `createElement` calls whose - // component is a lowercased variable, since the `restoreJSX` Babel - // plugin leaves them untouched. - return original - } - hasCompiledJsx = true - return ( - 'React.createElement(' + - // Assume `Fragment` is equivalent to `React.Fragment` so modules - // that use `import {Fragment} from 'react'` are reverse compiled. - (component === 'Fragment' ? 'React.Fragment' : component) - ) - }) + const reactJsxRE = new RegExp( + `\\b${reactAlias}\\.(createElement|Fragment)\\b`, + 'g' + ) - if (!hasCompiledJsx) { + if (!reactJsxRE.test(code)) { return jsxNotFound } @@ -73,7 +51,7 @@ export async function restoreJSX( parserOpts: { plugins: ['jsx'] }, - plugins: [await getBabelRestoreJSX()] + plugins: [[await getBabelRestoreJSX(), { reactAlias }]] }) return [result?.ast, isCommonJS]