Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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