From 5c9c8121e74dd102140852d23287c764eb39d4af Mon Sep 17 00:00:00 2001 From: "yulong.xiang" Date: Thu, 10 Feb 2022 16:13:44 +0800 Subject: [PATCH 1/3] fix(plugin-react): fix React is not defined when component name is lowercase When the react variables (such as React__default) of the code in our third-party dependencies are converted to React, and the component name is lowercase, it is not converted to jsx, which will cause React to be undefined after restoring the code. --- .../plugin-react/src/jsx-runtime/restore-jsx.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts index 268153f4ba5d45..f728ef32e7e4e7 100644 --- a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts +++ b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts @@ -24,17 +24,25 @@ export async function restoreJSX( return jsxNotFound } - const reactJsxRE = new RegExp( - '\\b' + reactAlias + '\\.(createElement|Fragment)\\b', + const reactJsxFragmentRE = new RegExp( + '\\b' + reactAlias + '\\.(Fragment)\\b', + 'g' + ) + const reactJsxCreatElementRE = new RegExp( + '\\b' + reactAlias + '\\.(createElement)\\b(\\([A-Z]\\w)', 'g' ) let hasCompiledJsx = false - code = code.replace(reactJsxRE, (_, prop) => { + code = code.replace(reactJsxFragmentRE, (_, prop) => { hasCompiledJsx = true // Replace with "React" so JSX can be reverse compiled. return 'React.' + prop }) + code = code.replace(reactJsxCreatElementRE, (_, prop, prop2) => { + hasCompiledJsx = true + return 'React.' + prop + prop2 + }) if (!hasCompiledJsx) { return jsxNotFound From 98d55e66aab183b0ad1774d2cfe0fdb6793c74ee Mon Sep 17 00:00:00 2001 From: "yulong.xiang" Date: Thu, 10 Feb 2022 18:38:52 +0800 Subject: [PATCH 2/3] fix(plugin-react): fix React is not defined when component name is lowercase add test --- .../src/jsx-runtime/restore-jsx.spec.ts | 55 +++++++++++++++++++ .../src/jsx-runtime/restore-jsx.ts | 3 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 packages/plugin-react/src/jsx-runtime/restore-jsx.spec.ts diff --git a/packages/plugin-react/src/jsx-runtime/restore-jsx.spec.ts b/packages/plugin-react/src/jsx-runtime/restore-jsx.spec.ts new file mode 100644 index 00000000000000..adbd7a60599c25 --- /dev/null +++ b/packages/plugin-react/src/jsx-runtime/restore-jsx.spec.ts @@ -0,0 +1,55 @@ +import { restoreJSX } from './restore-jsx' +import * as babel from '@babel/core' + +async function jsx(sourceCode: string) { + const [ast] = await restoreJSX(babel, sourceCode, 'test.js') + if (ast === null) { + return ast + } + const { code } = await babel.transformFromAstAsync(ast, null, { + configFile: false + }) + return code +} +// jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; +// React__default.createElement(Foo)`) +// Tests adapted from: https://github.com/flying-sheep/babel-plugin-transform-react-createelement-to-jsx/blob/63137b6/test/index.js +describe('restore-jsx', () => { + it('should trans to ', async () => { + expect( + await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; + React__default.createElement(foo)`) + ).toBeNull() + expect( + await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; + React__default.createElement("h1")`) + ).toMatch(`

;`) + expect( + await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; + React__default.createElement(Foo)`) + ).toMatch(`;`) + expect( + await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; + React__default.createElement(Foo.Bar)`) + ).toMatch(`;`) + expect( + await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; + React__default.createElement(Foo.Bar.Baz)`) + ).toMatch(`;`) + }) + + it('should handle props', async () => { + expect( + await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; + React__default.createElement(foo, {hi: there})`) + ).toBeNull() + expect( + await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; + React__default.createElement("h1", {hi: there})`) + ).toMatch(`

;`) + expect( + await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react'; + React__default.createElement(Foo, {hi: there})`) + ).toMatch(`;`) + }) +}) diff --git a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts index f728ef32e7e4e7..db5c651b1e5c15 100644 --- a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts +++ b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts @@ -20,6 +20,7 @@ export async function restoreJSX( } const [reactAlias, isCommonJS] = parseReactAlias(code) + if (!reactAlias) { return jsxNotFound } @@ -29,7 +30,7 @@ export async function restoreJSX( 'g' ) const reactJsxCreatElementRE = new RegExp( - '\\b' + reactAlias + '\\.(createElement)\\b(\\([A-Z]\\w)', + '\\b' + reactAlias + '\\.(createElement)\\b(\\([A-Z"]\\w)', 'g' ) From 37ca8f67dee72ff1913a0f7ec9d7997fff4eb779 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Thu, 10 Feb 2022 15:27:11 -0500 Subject: [PATCH 3/3] refactor some --- .../src/jsx-runtime/restore-jsx.ts | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts index db5c651b1e5c15..dbc2218d2343ff 100644 --- a/packages/plugin-react/src/jsx-runtime/restore-jsx.ts +++ b/packages/plugin-react/src/jsx-runtime/restore-jsx.ts @@ -25,36 +25,37 @@ export async function restoreJSX( return jsxNotFound } - const reactJsxFragmentRE = new RegExp( - '\\b' + reactAlias + '\\.(Fragment)\\b', - 'g' - ) - const reactJsxCreatElementRE = new RegExp( - '\\b' + reactAlias + '\\.(createElement)\\b(\\([A-Z"]\\w)', - 'g' - ) - let hasCompiledJsx = false - code = code.replace(reactJsxFragmentRE, (_, prop) => { - hasCompiledJsx = true - // Replace with "React" so JSX can be reverse compiled. - return 'React.' + prop - }) - code = code.replace(reactJsxCreatElementRE, (_, prop, prop2) => { - hasCompiledJsx = true - return 'React.' + prop + prop2 - }) + + 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) + ) + }) if (!hasCompiledJsx) { return jsxNotFound } - // Support modules that use `import {Fragment} from 'react'` - code = code.replace( - /createElement\(Fragment,/g, - 'createElement(React.Fragment,' - ) - babelRestoreJSX ||= import('./babel-restore-jsx') const result = await babel.transformAsync(code, {