/
restore-jsx.ts
96 lines (83 loc) · 2.51 KB
/
restore-jsx.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import type * as babelCore from '@babel/core'
type RestoredJSX = [
result: babelCore.types.File | null | undefined,
isCommonJS: boolean
]
let babelRestoreJSX: Promise<babelCore.PluginItem> | undefined
const jsxNotFound: RestoredJSX = [null, false]
async function getBabelRestoreJSX() {
if (!babelRestoreJSX)
babelRestoreJSX = import('./babel-restore-jsx').then((r) => {
const fn = r.default
if ('default' in fn)
// @ts-expect-error
return fn.default
return fn
})
return babelRestoreJSX
}
/** Restore JSX from `React.createElement` calls */
export async function restoreJSX(
babel: typeof babelCore,
code: string,
filename: string
): Promise<RestoredJSX> {
const [reactAlias, isCommonJS] = parseReactAlias(code)
if (!reactAlias) {
return jsxNotFound
}
let hasCompiledJsx = false
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
}
const result = await babel.transformAsync(code, {
babelrc: false,
configFile: false,
ast: true,
code: false,
filename,
parserOpts: {
plugins: ['jsx']
},
plugins: [await getBabelRestoreJSX()]
})
return [result?.ast, isCommonJS]
}
function parseReactAlias(
code: string
): [alias: string | undefined, isCommonJS: boolean] {
let match = code.match(
/\b(var|let|const) +(\w+) *= *require\(["']react["']\)/
)
if (match) {
return [match[2], true]
}
match = code.match(/^import (\w+).+? from ["']react["']/m)
if (match) {
return [match[1], false]
}
return [undefined, false]
}