-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
/
restore-jsx.ts
91 lines (77 loc) · 2.53 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
import type * as babelCore from '@babel/core'
import type { PluginItem, types as t } from '@babel/core'
type RestoredJSX = [result: t.File | null | undefined, isCommonJS: boolean]
let babelRestoreJSX: Promise<PluginItem> | undefined
const jsxNotFound: RestoredJSX = [null, false]
/** Restore JSX from `React.createElement` calls */
export async function restoreJSX(
babel: typeof babelCore,
code: string,
filename: string
): Promise<RestoredJSX> {
// Avoid parsing the optimized react-dom since it will never
// contain compiled JSX and it's a pretty big file (800kb).
if (filename.includes('/.vite/react-dom.js')) {
return jsxNotFound
}
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
}
babelRestoreJSX ||= import('./babel-restore-jsx')
const result = await babel.transformAsync(code, {
babelrc: false,
configFile: false,
ast: true,
code: false,
filename,
parserOpts: {
plugins: ['jsx']
},
// @ts-ignore
plugins: [(await babelRestoreJSX).default]
})
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]
}