Skip to content

Commit

Permalink
fix(plugin-react): React is not defined when component name is lowerc…
Browse files Browse the repository at this point in the history
…ase (#6838)
  • Loading branch information
xyl66 authored May 2, 2022
1 parent cf8a48a commit bf40e5c
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 16 deletions.
55 changes: 55 additions & 0 deletions packages/plugin-react/src/jsx-runtime/restore-jsx.spec.ts
Original file line number Diff line number Diff line change
@@ -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(`<h1 />;`)
expect(
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
React__default.createElement(Foo)`)
).toMatch(`<Foo />;`)
expect(
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
React__default.createElement(Foo.Bar)`)
).toMatch(`<Foo.Bar />;`)
expect(
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
React__default.createElement(Foo.Bar.Baz)`)
).toMatch(`<Foo.Bar.Baz />;`)
})

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(`<h1 hi={there} />;`)
expect(
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
React__default.createElement(Foo, {hi: there})`)
).toMatch(`<Foo hi={there} />;`)
})
})
42 changes: 26 additions & 16 deletions packages/plugin-react/src/jsx-runtime/restore-jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,42 @@ export async function restoreJSX(
}

const [reactAlias, isCommonJS] = parseReactAlias(code)

if (!reactAlias) {
return jsxNotFound
}

const reactJsxRE = new RegExp(
'\\b' + reactAlias + '\\.(createElement|Fragment)\\b',
'g'
)

let hasCompiledJsx = false
code = code.replace(reactJsxRE, (_, prop) => {
hasCompiledJsx = true
// Replace with "React" so JSX can be reverse compiled.
return 'React.' + prop
})

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, {
Expand Down

0 comments on commit bf40e5c

Please sign in to comment.