Skip to content

Commit bf40e5c

Browse files
authored
fix(plugin-react): React is not defined when component name is lowercase (#6838)
1 parent cf8a48a commit bf40e5c

File tree

2 files changed

+81
-16
lines changed

2 files changed

+81
-16
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { restoreJSX } from './restore-jsx'
2+
import * as babel from '@babel/core'
3+
4+
async function jsx(sourceCode: string) {
5+
const [ast] = await restoreJSX(babel, sourceCode, 'test.js')
6+
if (ast === null) {
7+
return ast
8+
}
9+
const { code } = await babel.transformFromAstAsync(ast, null, {
10+
configFile: false
11+
})
12+
return code
13+
}
14+
// jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
15+
// React__default.createElement(Foo)`)
16+
// Tests adapted from: https://github.com/flying-sheep/babel-plugin-transform-react-createelement-to-jsx/blob/63137b6/test/index.js
17+
describe('restore-jsx', () => {
18+
it('should trans to ', async () => {
19+
expect(
20+
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
21+
React__default.createElement(foo)`)
22+
).toBeNull()
23+
expect(
24+
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
25+
React__default.createElement("h1")`)
26+
).toMatch(`<h1 />;`)
27+
expect(
28+
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
29+
React__default.createElement(Foo)`)
30+
).toMatch(`<Foo />;`)
31+
expect(
32+
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
33+
React__default.createElement(Foo.Bar)`)
34+
).toMatch(`<Foo.Bar />;`)
35+
expect(
36+
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
37+
React__default.createElement(Foo.Bar.Baz)`)
38+
).toMatch(`<Foo.Bar.Baz />;`)
39+
})
40+
41+
it('should handle props', async () => {
42+
expect(
43+
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
44+
React__default.createElement(foo, {hi: there})`)
45+
).toBeNull()
46+
expect(
47+
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
48+
React__default.createElement("h1", {hi: there})`)
49+
).toMatch(`<h1 hi={there} />;`)
50+
expect(
51+
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
52+
React__default.createElement(Foo, {hi: there})`)
53+
).toMatch(`<Foo hi={there} />;`)
54+
})
55+
})

packages/plugin-react/src/jsx-runtime/restore-jsx.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,42 @@ export async function restoreJSX(
2020
}
2121

2222
const [reactAlias, isCommonJS] = parseReactAlias(code)
23+
2324
if (!reactAlias) {
2425
return jsxNotFound
2526
}
2627

27-
const reactJsxRE = new RegExp(
28-
'\\b' + reactAlias + '\\.(createElement|Fragment)\\b',
29-
'g'
30-
)
31-
3228
let hasCompiledJsx = false
33-
code = code.replace(reactJsxRE, (_, prop) => {
34-
hasCompiledJsx = true
35-
// Replace with "React" so JSX can be reverse compiled.
36-
return 'React.' + prop
37-
})
29+
30+
const fragmentPattern = `\\b${reactAlias}\\.Fragment\\b`
31+
const createElementPattern = `\\b${reactAlias}\\.createElement\\(\\s*([A-Z"'][\\w$.]*["']?)`
32+
33+
// Replace the alias with "React" so JSX can be reverse compiled.
34+
code = code
35+
.replace(new RegExp(fragmentPattern, 'g'), () => {
36+
hasCompiledJsx = true
37+
return 'React.Fragment'
38+
})
39+
.replace(new RegExp(createElementPattern, 'g'), (original, component) => {
40+
if (/^[a-z][\w$]*$/.test(component)) {
41+
// Take care not to replace the alias for `createElement` calls whose
42+
// component is a lowercased variable, since the `restoreJSX` Babel
43+
// plugin leaves them untouched.
44+
return original
45+
}
46+
hasCompiledJsx = true
47+
return (
48+
'React.createElement(' +
49+
// Assume `Fragment` is equivalent to `React.Fragment` so modules
50+
// that use `import {Fragment} from 'react'` are reverse compiled.
51+
(component === 'Fragment' ? 'React.Fragment' : component)
52+
)
53+
})
3854

3955
if (!hasCompiledJsx) {
4056
return jsxNotFound
4157
}
4258

43-
// Support modules that use `import {Fragment} from 'react'`
44-
code = code.replace(
45-
/createElement\(Fragment,/g,
46-
'createElement(React.Fragment,'
47-
)
48-
4959
babelRestoreJSX ||= import('./babel-restore-jsx')
5060

5161
const result = await babel.transformAsync(code, {

0 commit comments

Comments
 (0)