Skip to content

Commit 8a5b575

Browse files
authored
fix(plugin-react): wrong substitution causes React is not defined (#9386)
1 parent 4e6a77f commit 8a5b575

File tree

3 files changed

+48
-33
lines changed

3 files changed

+48
-33
lines changed

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
*/
55
import type * as babel from '@babel/core'
66

7+
interface PluginOptions {
8+
reactAlias: string
9+
}
10+
711
/**
812
* Visitor factory for babel, converting React.createElement(...) to <jsx ...>...</jsx>
913
*
@@ -17,7 +21,10 @@ import type * as babel from '@babel/core'
1721
*
1822
* Any of those arguments might also be missing (undefined) and/or invalid.
1923
*/
20-
export default function ({ types: t }: typeof babel): babel.PluginObj {
24+
export default function (
25+
{ types: t }: typeof babel,
26+
{ reactAlias = 'React' }: PluginOptions
27+
): babel.PluginObj {
2128
/**
2229
* Get a `JSXElement` from a `CallExpression`.
2330
* Returns `null` if this impossible.
@@ -48,7 +55,7 @@ export default function ({ types: t }: typeof babel): babel.PluginObj {
4855
if (
4956
t.isJSXMemberExpression(name) &&
5057
t.isJSXIdentifier(name.object) &&
51-
name.object.name === 'React' &&
58+
name.object.name === reactAlias &&
5259
name.property.name === 'Fragment'
5360
) {
5461
return t.jsxFragment(
@@ -182,7 +189,7 @@ export default function ({ types: t }: typeof babel): babel.PluginObj {
182189
const isReactCreateElement = (node: any) =>
183190
t.isCallExpression(node) &&
184191
t.isMemberExpression(node.callee) &&
185-
t.isIdentifier(node.callee.object, { name: 'React' }) &&
192+
t.isIdentifier(node.callee.object, { name: reactAlias }) &&
186193
t.isIdentifier(node.callee.property, { name: 'createElement' }) &&
187194
!node.callee.computed
188195

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,10 @@ describe('restore-jsx', () => {
8181
expect(
8282
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
8383
React__default.createElement(foo)`)
84-
).toBeNull()
84+
).toMatchInlineSnapshot(`
85+
"import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
86+
React__default.createElement(foo);"
87+
`)
8588
expect(
8689
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
8790
React__default.createElement("h1")`)
@@ -104,7 +107,12 @@ describe('restore-jsx', () => {
104107
expect(
105108
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
106109
React__default.createElement(foo, {hi: there})`)
107-
).toBeNull()
110+
).toMatchInlineSnapshot(`
111+
"import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
112+
React__default.createElement(foo, {
113+
hi: there
114+
});"
115+
`)
108116
expect(
109117
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
110118
React__default.createElement("h1", {hi: there})`)
@@ -114,4 +122,26 @@ describe('restore-jsx', () => {
114122
React__default.createElement(Foo, {hi: there})`)
115123
).toMatch(`<Foo hi={there} />;`)
116124
})
125+
126+
it('should handle Fragment', async () => {
127+
expect(
128+
await jsx(`import R, { Fragment } from 'react';
129+
R.createElement(Fragment)
130+
`)
131+
).toMatchInlineSnapshot(`
132+
"import R, { Fragment } from 'react';
133+
<Fragment />;"
134+
`)
135+
})
136+
137+
it('should handle Fragment alias', async () => {
138+
expect(
139+
await jsx(`import RA, { Fragment as F } from 'react';
140+
RA.createElement(F, null, RA.createElement(RA.Fragment))
141+
`)
142+
).toMatchInlineSnapshot(`
143+
"import RA, { Fragment as F } from 'react';
144+
<F><></></F>;"
145+
`)
146+
})
117147
})

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

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,12 @@ export async function restoreJSX(
3333
return jsxNotFound
3434
}
3535

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

63-
if (!hasCompiledJsx) {
41+
if (!reactJsxRE.test(code)) {
6442
return jsxNotFound
6543
}
6644

@@ -73,7 +51,7 @@ export async function restoreJSX(
7351
parserOpts: {
7452
plugins: ['jsx']
7553
},
76-
plugins: [await getBabelRestoreJSX()]
54+
plugins: [[await getBabelRestoreJSX(), { reactAlias }]]
7755
})
7856

7957
return [result?.ast, isCommonJS]

0 commit comments

Comments
 (0)