@vitejs/plugin-react 2.2.0-beta.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/babel-restore-jsx.cjs +1 -1
- package/dist/index.cjs +9 -15
- package/package.json +5 -6
- package/src/babel.d.ts +0 -4
- package/src/fast-refresh.ts +0 -155
- package/src/index.ts +0 -479
- package/src/jsx-runtime/babel-import-to-require.ts +0 -35
- package/src/jsx-runtime/babel-restore-jsx.spec.ts +0 -132
- package/src/jsx-runtime/babel-restore-jsx.ts +0 -232
- package/src/jsx-runtime/restore-jsx.spec.ts +0 -147
- package/src/jsx-runtime/restore-jsx.ts +0 -74
package/dist/index.cjs
CHANGED
@@ -7,37 +7,31 @@ const MagicString = require('magic-string');
|
|
7
7
|
const fs = require('node:fs');
|
8
8
|
const node_module = require('node:module');
|
9
9
|
|
10
|
-
function
|
11
|
-
|
12
|
-
function _interopNamespace(e) {
|
13
|
-
if (e && e.__esModule) return e;
|
10
|
+
function _interopNamespaceDefault(e) {
|
14
11
|
const n = Object.create(null);
|
15
12
|
if (e) {
|
16
13
|
for (const k in e) {
|
17
14
|
n[k] = e[k];
|
18
15
|
}
|
19
16
|
}
|
20
|
-
n
|
17
|
+
n.default = e;
|
21
18
|
return n;
|
22
19
|
}
|
23
20
|
|
24
|
-
const
|
25
|
-
const babel__namespace = /*#__PURE__*/_interopNamespace(babel);
|
26
|
-
const MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString);
|
27
|
-
const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
21
|
+
const babel__namespace = /*#__PURE__*/_interopNamespaceDefault(babel);
|
28
22
|
|
29
23
|
const runtimePublicPath = "/@react-refresh";
|
30
24
|
const _require = node_module.createRequire((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)));
|
31
|
-
const reactRefreshDir =
|
25
|
+
const reactRefreshDir = path.dirname(
|
32
26
|
_require.resolve("react-refresh/package.json")
|
33
27
|
);
|
34
|
-
const runtimeFilePath =
|
28
|
+
const runtimeFilePath = path.join(
|
35
29
|
reactRefreshDir,
|
36
30
|
"cjs/react-refresh-runtime.development.js"
|
37
31
|
);
|
38
32
|
const runtimeCode = `
|
39
33
|
const exports = {}
|
40
|
-
${
|
34
|
+
${fs.readFileSync(runtimeFilePath, "utf-8")}
|
41
35
|
function debounce(fn, delay) {
|
42
36
|
let handle
|
43
37
|
return () => {
|
@@ -262,7 +256,7 @@ function viteReact(opts = {}) {
|
|
262
256
|
configResolved(config) {
|
263
257
|
devBase = config.base;
|
264
258
|
projectRoot = config.root;
|
265
|
-
resolvedCacheDir = vite.normalizePath(
|
259
|
+
resolvedCacheDir = vite.normalizePath(path.resolve(config.cacheDir));
|
266
260
|
filter = vite.createFilter(opts.include, opts.exclude, {
|
267
261
|
resolve: projectRoot
|
268
262
|
});
|
@@ -363,7 +357,7 @@ function viteReact(opts = {}) {
|
|
363
357
|
let inputMap;
|
364
358
|
if (prependReactImport) {
|
365
359
|
if (needHiresSourcemap) {
|
366
|
-
const s = new
|
360
|
+
const s = new MagicString(code);
|
367
361
|
s.prepend(prependReactImportCode);
|
368
362
|
code = s.toString();
|
369
363
|
inputMap = s.generateMap({ hires: true, source: id });
|
@@ -519,4 +513,4 @@ function createBabelOptions(rawOptions) {
|
|
519
513
|
}
|
520
514
|
|
521
515
|
module.exports = viteReact;
|
522
|
-
module.exports
|
516
|
+
module.exports.default = viteReact;
|
package/package.json
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vitejs/plugin-react",
|
3
|
-
"version": "2.2.0
|
3
|
+
"version": "2.2.0",
|
4
4
|
"license": "MIT",
|
5
5
|
"author": "Evan You",
|
6
6
|
"contributors": [
|
7
7
|
"Alec Larson"
|
8
8
|
],
|
9
9
|
"files": [
|
10
|
-
"dist"
|
11
|
-
"src"
|
10
|
+
"dist"
|
12
11
|
],
|
13
12
|
"main": "./dist/index.cjs",
|
14
13
|
"module": "./dist/index.mjs",
|
@@ -39,12 +38,12 @@
|
|
39
38
|
},
|
40
39
|
"homepage": "https://github.com/vitejs/vite/tree/main/packages/plugin-react#readme",
|
41
40
|
"dependencies": {
|
42
|
-
"@babel/core": "^7.19.
|
41
|
+
"@babel/core": "^7.19.6",
|
43
42
|
"@babel/plugin-transform-react-jsx": "^7.19.0",
|
44
43
|
"@babel/plugin-transform-react-jsx-development": "^7.18.6",
|
45
44
|
"@babel/plugin-transform-react-jsx-self": "^7.18.6",
|
46
|
-
"@babel/plugin-transform-react-jsx-source": "^7.
|
47
|
-
"magic-string": "^0.26.
|
45
|
+
"@babel/plugin-transform-react-jsx-source": "^7.19.6",
|
46
|
+
"magic-string": "^0.26.7",
|
48
47
|
"react-refresh": "^0.14.0"
|
49
48
|
},
|
50
49
|
"peerDependencies": {
|
package/src/babel.d.ts
DELETED
package/src/fast-refresh.ts
DELETED
@@ -1,155 +0,0 @@
|
|
1
|
-
import fs from 'node:fs'
|
2
|
-
import path from 'node:path'
|
3
|
-
import { createRequire } from 'node:module'
|
4
|
-
import type { types as t } from '@babel/core'
|
5
|
-
|
6
|
-
export const runtimePublicPath = '/@react-refresh'
|
7
|
-
|
8
|
-
const _require = createRequire(import.meta.url)
|
9
|
-
const reactRefreshDir = path.dirname(
|
10
|
-
_require.resolve('react-refresh/package.json')
|
11
|
-
)
|
12
|
-
const runtimeFilePath = path.join(
|
13
|
-
reactRefreshDir,
|
14
|
-
'cjs/react-refresh-runtime.development.js'
|
15
|
-
)
|
16
|
-
|
17
|
-
export const runtimeCode = `
|
18
|
-
const exports = {}
|
19
|
-
${fs.readFileSync(runtimeFilePath, 'utf-8')}
|
20
|
-
function debounce(fn, delay) {
|
21
|
-
let handle
|
22
|
-
return () => {
|
23
|
-
clearTimeout(handle)
|
24
|
-
handle = setTimeout(fn, delay)
|
25
|
-
}
|
26
|
-
}
|
27
|
-
exports.performReactRefresh = debounce(exports.performReactRefresh, 16)
|
28
|
-
export default exports
|
29
|
-
`
|
30
|
-
|
31
|
-
export const preambleCode = `
|
32
|
-
import RefreshRuntime from "__BASE__${runtimePublicPath.slice(1)}"
|
33
|
-
RefreshRuntime.injectIntoGlobalHook(window)
|
34
|
-
window.$RefreshReg$ = () => {}
|
35
|
-
window.$RefreshSig$ = () => (type) => type
|
36
|
-
window.__vite_plugin_react_preamble_installed__ = true
|
37
|
-
`
|
38
|
-
|
39
|
-
const header = `
|
40
|
-
import RefreshRuntime from "${runtimePublicPath}";
|
41
|
-
|
42
|
-
let prevRefreshReg;
|
43
|
-
let prevRefreshSig;
|
44
|
-
|
45
|
-
if (import.meta.hot) {
|
46
|
-
if (!window.__vite_plugin_react_preamble_installed__) {
|
47
|
-
throw new Error(
|
48
|
-
"@vitejs/plugin-react can't detect preamble. Something is wrong. " +
|
49
|
-
"See https://github.com/vitejs/vite-plugin-react/pull/11#discussion_r430879201"
|
50
|
-
);
|
51
|
-
}
|
52
|
-
|
53
|
-
prevRefreshReg = window.$RefreshReg$;
|
54
|
-
prevRefreshSig = window.$RefreshSig$;
|
55
|
-
window.$RefreshReg$ = (type, id) => {
|
56
|
-
RefreshRuntime.register(type, __SOURCE__ + " " + id)
|
57
|
-
};
|
58
|
-
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
|
59
|
-
}`.replace(/[\n]+/gm, '')
|
60
|
-
|
61
|
-
const timeout = `
|
62
|
-
if (!window.__vite_plugin_react_timeout) {
|
63
|
-
window.__vite_plugin_react_timeout = setTimeout(() => {
|
64
|
-
window.__vite_plugin_react_timeout = 0;
|
65
|
-
RefreshRuntime.performReactRefresh();
|
66
|
-
}, 30);
|
67
|
-
}
|
68
|
-
`
|
69
|
-
|
70
|
-
const footer = `
|
71
|
-
if (import.meta.hot) {
|
72
|
-
window.$RefreshReg$ = prevRefreshReg;
|
73
|
-
window.$RefreshSig$ = prevRefreshSig;
|
74
|
-
|
75
|
-
__ACCEPT__
|
76
|
-
}`
|
77
|
-
|
78
|
-
const checkAndAccept = `
|
79
|
-
function isReactRefreshBoundary(mod) {
|
80
|
-
if (mod == null || typeof mod !== 'object') {
|
81
|
-
return false;
|
82
|
-
}
|
83
|
-
let hasExports = false;
|
84
|
-
let areAllExportsComponents = true;
|
85
|
-
for (const exportName in mod) {
|
86
|
-
hasExports = true;
|
87
|
-
if (exportName === '__esModule') {
|
88
|
-
continue;
|
89
|
-
}
|
90
|
-
const desc = Object.getOwnPropertyDescriptor(mod, exportName);
|
91
|
-
if (desc && desc.get) {
|
92
|
-
// Don't invoke getters as they may have side effects.
|
93
|
-
return false;
|
94
|
-
}
|
95
|
-
const exportValue = mod[exportName];
|
96
|
-
if (!RefreshRuntime.isLikelyComponentType(exportValue)) {
|
97
|
-
areAllExportsComponents = false;
|
98
|
-
}
|
99
|
-
}
|
100
|
-
return hasExports && areAllExportsComponents;
|
101
|
-
}
|
102
|
-
|
103
|
-
import.meta.hot.accept(mod => {
|
104
|
-
if (isReactRefreshBoundary(mod)) {
|
105
|
-
${timeout}
|
106
|
-
} else {
|
107
|
-
import.meta.hot.invalidate();
|
108
|
-
}
|
109
|
-
});
|
110
|
-
`
|
111
|
-
|
112
|
-
export function addRefreshWrapper(
|
113
|
-
code: string,
|
114
|
-
id: string,
|
115
|
-
accept: boolean
|
116
|
-
): string {
|
117
|
-
return (
|
118
|
-
header.replace('__SOURCE__', JSON.stringify(id)) +
|
119
|
-
code +
|
120
|
-
footer.replace('__ACCEPT__', accept ? checkAndAccept : timeout)
|
121
|
-
)
|
122
|
-
}
|
123
|
-
|
124
|
-
export function isRefreshBoundary(ast: t.File): boolean {
|
125
|
-
// Every export must be a potential React component.
|
126
|
-
// We'll also perform a runtime check that's more robust as well (isLikelyComponentType).
|
127
|
-
return ast.program.body.every((node) => {
|
128
|
-
if (node.type !== 'ExportNamedDeclaration') {
|
129
|
-
return true
|
130
|
-
}
|
131
|
-
const { declaration, specifiers } = node
|
132
|
-
if (declaration) {
|
133
|
-
if (declaration.type === 'ClassDeclaration') return false
|
134
|
-
if (declaration.type === 'VariableDeclaration') {
|
135
|
-
return declaration.declarations.every((variable) =>
|
136
|
-
isComponentLikeIdentifier(variable.id)
|
137
|
-
)
|
138
|
-
}
|
139
|
-
if (declaration.type === 'FunctionDeclaration') {
|
140
|
-
return !!declaration.id && isComponentLikeIdentifier(declaration.id)
|
141
|
-
}
|
142
|
-
}
|
143
|
-
return specifiers.every((spec) => {
|
144
|
-
return isComponentLikeIdentifier(spec.exported)
|
145
|
-
})
|
146
|
-
})
|
147
|
-
}
|
148
|
-
|
149
|
-
function isComponentLikeIdentifier(node: t.Node): boolean {
|
150
|
-
return node.type === 'Identifier' && isComponentLikeName(node.name)
|
151
|
-
}
|
152
|
-
|
153
|
-
function isComponentLikeName(name: string): boolean {
|
154
|
-
return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z'
|
155
|
-
}
|
package/src/index.ts
DELETED
@@ -1,479 +0,0 @@
|
|
1
|
-
import path from 'node:path'
|
2
|
-
import type { ParserOptions, TransformOptions, types as t } from '@babel/core'
|
3
|
-
import * as babel from '@babel/core'
|
4
|
-
import { createFilter, normalizePath } from 'vite'
|
5
|
-
import type { Plugin, PluginOption, ResolvedConfig } from 'vite'
|
6
|
-
import MagicString from 'magic-string'
|
7
|
-
import type { SourceMap } from 'magic-string'
|
8
|
-
import {
|
9
|
-
addRefreshWrapper,
|
10
|
-
isRefreshBoundary,
|
11
|
-
preambleCode,
|
12
|
-
runtimeCode,
|
13
|
-
runtimePublicPath
|
14
|
-
} from './fast-refresh'
|
15
|
-
import { babelImportToRequire } from './jsx-runtime/babel-import-to-require'
|
16
|
-
import { restoreJSX } from './jsx-runtime/restore-jsx'
|
17
|
-
|
18
|
-
export interface Options {
|
19
|
-
include?: string | RegExp | Array<string | RegExp>
|
20
|
-
exclude?: string | RegExp | Array<string | RegExp>
|
21
|
-
/**
|
22
|
-
* Enable `react-refresh` integration. Vite disables this in prod env or build mode.
|
23
|
-
* @default true
|
24
|
-
*/
|
25
|
-
fastRefresh?: boolean
|
26
|
-
/**
|
27
|
-
* Set this to `"automatic"` to use [vite-react-jsx](https://github.com/alloc/vite-react-jsx).
|
28
|
-
* @default "automatic"
|
29
|
-
*/
|
30
|
-
jsxRuntime?: 'classic' | 'automatic'
|
31
|
-
/**
|
32
|
-
* Control where the JSX factory is imported from.
|
33
|
-
* This option is ignored when `jsxRuntime` is not `"automatic"`.
|
34
|
-
* @default "react"
|
35
|
-
*/
|
36
|
-
jsxImportSource?: string
|
37
|
-
/**
|
38
|
-
* Set this to `true` to annotate the JSX factory with `\/* @__PURE__ *\/`.
|
39
|
-
* This option is ignored when `jsxRuntime` is not `"automatic"`.
|
40
|
-
* @default true
|
41
|
-
*/
|
42
|
-
jsxPure?: boolean
|
43
|
-
/**
|
44
|
-
* Toggles whether or not to throw an error if an XML namespaced tag name is used.
|
45
|
-
* @default true
|
46
|
-
*/
|
47
|
-
jsxThrowIfNamespace?: boolean
|
48
|
-
/**
|
49
|
-
* Babel configuration applied in both dev and prod.
|
50
|
-
*/
|
51
|
-
babel?:
|
52
|
-
| BabelOptions
|
53
|
-
| ((id: string, options: { ssr?: boolean }) => BabelOptions)
|
54
|
-
}
|
55
|
-
|
56
|
-
export type BabelOptions = Omit<
|
57
|
-
TransformOptions,
|
58
|
-
| 'ast'
|
59
|
-
| 'filename'
|
60
|
-
| 'root'
|
61
|
-
| 'sourceFileName'
|
62
|
-
| 'sourceMaps'
|
63
|
-
| 'inputSourceMap'
|
64
|
-
>
|
65
|
-
|
66
|
-
/**
|
67
|
-
* The object type used by the `options` passed to plugins with
|
68
|
-
* an `api.reactBabel` method.
|
69
|
-
*/
|
70
|
-
export interface ReactBabelOptions extends BabelOptions {
|
71
|
-
plugins: Extract<BabelOptions['plugins'], any[]>
|
72
|
-
presets: Extract<BabelOptions['presets'], any[]>
|
73
|
-
overrides: Extract<BabelOptions['overrides'], any[]>
|
74
|
-
parserOpts: ParserOptions & {
|
75
|
-
plugins: Extract<ParserOptions['plugins'], any[]>
|
76
|
-
}
|
77
|
-
}
|
78
|
-
|
79
|
-
type ReactBabelHook = (
|
80
|
-
babelConfig: ReactBabelOptions,
|
81
|
-
context: ReactBabelHookContext,
|
82
|
-
config: ResolvedConfig
|
83
|
-
) => void
|
84
|
-
|
85
|
-
type ReactBabelHookContext = { ssr: boolean; id: string }
|
86
|
-
|
87
|
-
declare module 'vite' {
|
88
|
-
export interface Plugin {
|
89
|
-
api?: {
|
90
|
-
/**
|
91
|
-
* Manipulate the Babel options of `@vitejs/plugin-react`
|
92
|
-
*/
|
93
|
-
reactBabel?: ReactBabelHook
|
94
|
-
}
|
95
|
-
}
|
96
|
-
}
|
97
|
-
|
98
|
-
const prependReactImportCode = "import React from 'react'; "
|
99
|
-
|
100
|
-
export default function viteReact(opts: Options = {}): PluginOption[] {
|
101
|
-
// Provide default values for Rollup compat.
|
102
|
-
let devBase = '/'
|
103
|
-
let resolvedCacheDir: string
|
104
|
-
let filter = createFilter(opts.include, opts.exclude)
|
105
|
-
let needHiresSourcemap = false
|
106
|
-
let isProduction = true
|
107
|
-
let projectRoot = process.cwd()
|
108
|
-
let skipFastRefresh = opts.fastRefresh === false
|
109
|
-
let skipReactImport = false
|
110
|
-
let runPluginOverrides = (
|
111
|
-
options: ReactBabelOptions,
|
112
|
-
context: ReactBabelHookContext
|
113
|
-
) => false
|
114
|
-
let staticBabelOptions: ReactBabelOptions | undefined
|
115
|
-
|
116
|
-
const useAutomaticRuntime = opts.jsxRuntime !== 'classic'
|
117
|
-
|
118
|
-
// Support patterns like:
|
119
|
-
// - import * as React from 'react';
|
120
|
-
// - import React from 'react';
|
121
|
-
// - import React, {useEffect} from 'react';
|
122
|
-
const importReactRE = /(^|\n)import\s+(\*\s+as\s+)?React(,|\s+)/
|
123
|
-
|
124
|
-
// Any extension, including compound ones like '.bs.js'
|
125
|
-
const fileExtensionRE = /\.[^\/\s\?]+$/
|
126
|
-
|
127
|
-
const viteBabel: Plugin = {
|
128
|
-
name: 'vite:react-babel',
|
129
|
-
enforce: 'pre',
|
130
|
-
config() {
|
131
|
-
if (opts.jsxRuntime === 'classic') {
|
132
|
-
return {
|
133
|
-
esbuild: {
|
134
|
-
logOverride: {
|
135
|
-
'this-is-undefined-in-esm': 'silent'
|
136
|
-
}
|
137
|
-
}
|
138
|
-
}
|
139
|
-
}
|
140
|
-
},
|
141
|
-
configResolved(config) {
|
142
|
-
devBase = config.base
|
143
|
-
projectRoot = config.root
|
144
|
-
resolvedCacheDir = normalizePath(path.resolve(config.cacheDir))
|
145
|
-
filter = createFilter(opts.include, opts.exclude, {
|
146
|
-
resolve: projectRoot
|
147
|
-
})
|
148
|
-
needHiresSourcemap =
|
149
|
-
config.command === 'build' && !!config.build.sourcemap
|
150
|
-
isProduction = config.isProduction
|
151
|
-
skipFastRefresh ||= isProduction || config.command === 'build'
|
152
|
-
|
153
|
-
const jsxInject = config.esbuild && config.esbuild.jsxInject
|
154
|
-
if (jsxInject && importReactRE.test(jsxInject)) {
|
155
|
-
skipReactImport = true
|
156
|
-
config.logger.warn(
|
157
|
-
'[@vitejs/plugin-react] This plugin imports React for you automatically,' +
|
158
|
-
' so you can stop using `esbuild.jsxInject` for that purpose.'
|
159
|
-
)
|
160
|
-
}
|
161
|
-
|
162
|
-
config.plugins.forEach((plugin) => {
|
163
|
-
const hasConflict =
|
164
|
-
plugin.name === 'react-refresh' ||
|
165
|
-
(plugin !== viteReactJsx && plugin.name === 'vite:react-jsx')
|
166
|
-
|
167
|
-
if (hasConflict)
|
168
|
-
return config.logger.warn(
|
169
|
-
`[@vitejs/plugin-react] You should stop using "${plugin.name}" ` +
|
170
|
-
`since this plugin conflicts with it.`
|
171
|
-
)
|
172
|
-
})
|
173
|
-
|
174
|
-
runPluginOverrides = (babelOptions, context) => {
|
175
|
-
const hooks = config.plugins
|
176
|
-
.map((plugin) => plugin.api?.reactBabel)
|
177
|
-
.filter(Boolean) as ReactBabelHook[]
|
178
|
-
|
179
|
-
if (hooks.length > 0) {
|
180
|
-
return (runPluginOverrides = (babelOptions, context) => {
|
181
|
-
hooks.forEach((hook) => hook(babelOptions, context, config))
|
182
|
-
return true
|
183
|
-
})(babelOptions, context)
|
184
|
-
}
|
185
|
-
runPluginOverrides = () => false
|
186
|
-
return false
|
187
|
-
}
|
188
|
-
},
|
189
|
-
async transform(code, id, options) {
|
190
|
-
const ssr = options?.ssr === true
|
191
|
-
// File extension could be mocked/overridden in querystring.
|
192
|
-
const [filepath, querystring = ''] = id.split('?')
|
193
|
-
const [extension = ''] =
|
194
|
-
querystring.match(fileExtensionRE) ||
|
195
|
-
filepath.match(fileExtensionRE) ||
|
196
|
-
[]
|
197
|
-
|
198
|
-
if (/\.(mjs|[tj]sx?)$/.test(extension)) {
|
199
|
-
const isJSX = extension.endsWith('x')
|
200
|
-
const isNodeModules = id.includes('/node_modules/')
|
201
|
-
const isProjectFile =
|
202
|
-
!isNodeModules && (id[0] === '\0' || id.startsWith(projectRoot + '/'))
|
203
|
-
|
204
|
-
let babelOptions = staticBabelOptions
|
205
|
-
if (typeof opts.babel === 'function') {
|
206
|
-
const rawOptions = opts.babel(id, { ssr })
|
207
|
-
babelOptions = createBabelOptions(rawOptions)
|
208
|
-
runPluginOverrides(babelOptions, { ssr, id: id })
|
209
|
-
} else if (!babelOptions) {
|
210
|
-
babelOptions = createBabelOptions(opts.babel)
|
211
|
-
if (!runPluginOverrides(babelOptions, { ssr, id: id })) {
|
212
|
-
staticBabelOptions = babelOptions
|
213
|
-
}
|
214
|
-
}
|
215
|
-
|
216
|
-
const plugins = isProjectFile ? [...babelOptions.plugins] : []
|
217
|
-
|
218
|
-
let useFastRefresh = false
|
219
|
-
if (!skipFastRefresh && !ssr && !isNodeModules) {
|
220
|
-
// Modules with .js or .ts extension must import React.
|
221
|
-
const isReactModule = isJSX || importReactRE.test(code)
|
222
|
-
if (isReactModule && filter(id)) {
|
223
|
-
useFastRefresh = true
|
224
|
-
plugins.push([
|
225
|
-
await loadPlugin('react-refresh/babel'),
|
226
|
-
{ skipEnvCheck: true }
|
227
|
-
])
|
228
|
-
}
|
229
|
-
}
|
230
|
-
|
231
|
-
let ast: t.File | null | undefined
|
232
|
-
let prependReactImport = false
|
233
|
-
if (!isProjectFile || isJSX) {
|
234
|
-
if (useAutomaticRuntime) {
|
235
|
-
// By reverse-compiling "React.createElement" calls into JSX,
|
236
|
-
// React elements provided by dependencies will also use the
|
237
|
-
// automatic runtime!
|
238
|
-
// Avoid parsing the optimized react-dom since it will never
|
239
|
-
// contain compiled JSX and it's a pretty big file (800kb).
|
240
|
-
const isOptimizedReactDom =
|
241
|
-
id.startsWith(resolvedCacheDir) && id.includes('/react-dom.js')
|
242
|
-
const [restoredAst, isCommonJS] =
|
243
|
-
!isProjectFile && !isJSX && !isOptimizedReactDom
|
244
|
-
? await restoreJSX(babel, code, id)
|
245
|
-
: [null, false]
|
246
|
-
|
247
|
-
if (isJSX || (ast = restoredAst)) {
|
248
|
-
plugins.push([
|
249
|
-
await loadPlugin(
|
250
|
-
'@babel/plugin-transform-react-jsx' +
|
251
|
-
(isProduction ? '' : '-development')
|
252
|
-
),
|
253
|
-
{
|
254
|
-
runtime: 'automatic',
|
255
|
-
importSource: opts.jsxImportSource,
|
256
|
-
pure: opts.jsxPure !== false,
|
257
|
-
throwIfNamespace: opts.jsxThrowIfNamespace
|
258
|
-
}
|
259
|
-
])
|
260
|
-
|
261
|
-
// Avoid inserting `import` statements into CJS modules.
|
262
|
-
if (isCommonJS) {
|
263
|
-
plugins.push(babelImportToRequire)
|
264
|
-
}
|
265
|
-
}
|
266
|
-
} else if (isProjectFile) {
|
267
|
-
// These plugins are only needed for the classic runtime.
|
268
|
-
if (!isProduction) {
|
269
|
-
plugins.push(
|
270
|
-
await loadPlugin('@babel/plugin-transform-react-jsx-self'),
|
271
|
-
await loadPlugin('@babel/plugin-transform-react-jsx-source')
|
272
|
-
)
|
273
|
-
}
|
274
|
-
|
275
|
-
// Even if the automatic JSX runtime is not used, we can still
|
276
|
-
// inject the React import for .jsx and .tsx modules.
|
277
|
-
if (!skipReactImport && !importReactRE.test(code)) {
|
278
|
-
prependReactImport = true
|
279
|
-
}
|
280
|
-
}
|
281
|
-
}
|
282
|
-
|
283
|
-
let inputMap: SourceMap | undefined
|
284
|
-
if (prependReactImport) {
|
285
|
-
if (needHiresSourcemap) {
|
286
|
-
const s = new MagicString(code)
|
287
|
-
s.prepend(prependReactImportCode)
|
288
|
-
code = s.toString()
|
289
|
-
inputMap = s.generateMap({ hires: true, source: id })
|
290
|
-
} else {
|
291
|
-
code = prependReactImportCode + code
|
292
|
-
}
|
293
|
-
}
|
294
|
-
|
295
|
-
// Plugins defined through this Vite plugin are only applied
|
296
|
-
// to modules within the project root, but "babel.config.js"
|
297
|
-
// files can define plugins that need to be applied to every
|
298
|
-
// module, including node_modules and linked packages.
|
299
|
-
const shouldSkip =
|
300
|
-
!plugins.length &&
|
301
|
-
!babelOptions.configFile &&
|
302
|
-
!(isProjectFile && babelOptions.babelrc)
|
303
|
-
|
304
|
-
// Avoid parsing if no plugins exist.
|
305
|
-
if (shouldSkip) {
|
306
|
-
return {
|
307
|
-
code,
|
308
|
-
map: inputMap ?? null
|
309
|
-
}
|
310
|
-
}
|
311
|
-
|
312
|
-
const parserPlugins: typeof babelOptions.parserOpts.plugins = [
|
313
|
-
...babelOptions.parserOpts.plugins,
|
314
|
-
'importMeta',
|
315
|
-
// This plugin is applied before esbuild transforms the code,
|
316
|
-
// so we need to enable some stage 3 syntax that is supported in
|
317
|
-
// TypeScript and some environments already.
|
318
|
-
'topLevelAwait',
|
319
|
-
'classProperties',
|
320
|
-
'classPrivateProperties',
|
321
|
-
'classPrivateMethods'
|
322
|
-
]
|
323
|
-
|
324
|
-
if (!extension.endsWith('.ts')) {
|
325
|
-
parserPlugins.push('jsx')
|
326
|
-
}
|
327
|
-
|
328
|
-
if (/\.tsx?$/.test(extension)) {
|
329
|
-
parserPlugins.push('typescript')
|
330
|
-
}
|
331
|
-
|
332
|
-
const transformAsync = ast
|
333
|
-
? babel.transformFromAstAsync.bind(babel, ast, code)
|
334
|
-
: babel.transformAsync.bind(babel, code)
|
335
|
-
|
336
|
-
const isReasonReact = extension.endsWith('.bs.js')
|
337
|
-
const result = await transformAsync({
|
338
|
-
...babelOptions,
|
339
|
-
ast: !isReasonReact,
|
340
|
-
root: projectRoot,
|
341
|
-
filename: id,
|
342
|
-
sourceFileName: filepath,
|
343
|
-
parserOpts: {
|
344
|
-
...babelOptions.parserOpts,
|
345
|
-
sourceType: 'module',
|
346
|
-
allowAwaitOutsideFunction: true,
|
347
|
-
plugins: parserPlugins
|
348
|
-
},
|
349
|
-
generatorOpts: {
|
350
|
-
...babelOptions.generatorOpts,
|
351
|
-
decoratorsBeforeExport: true
|
352
|
-
},
|
353
|
-
plugins,
|
354
|
-
sourceMaps: true,
|
355
|
-
// Vite handles sourcemap flattening
|
356
|
-
inputSourceMap: inputMap ?? (false as any)
|
357
|
-
})
|
358
|
-
|
359
|
-
if (result) {
|
360
|
-
let code = result.code!
|
361
|
-
if (useFastRefresh && /\$RefreshReg\$\(/.test(code)) {
|
362
|
-
const accept = isReasonReact || isRefreshBoundary(result.ast!)
|
363
|
-
code = addRefreshWrapper(code, id, accept)
|
364
|
-
}
|
365
|
-
return {
|
366
|
-
code,
|
367
|
-
map: result.map
|
368
|
-
}
|
369
|
-
}
|
370
|
-
}
|
371
|
-
}
|
372
|
-
}
|
373
|
-
|
374
|
-
const viteReactRefresh: Plugin = {
|
375
|
-
name: 'vite:react-refresh',
|
376
|
-
enforce: 'pre',
|
377
|
-
config: () => ({
|
378
|
-
resolve: {
|
379
|
-
dedupe: ['react', 'react-dom']
|
380
|
-
}
|
381
|
-
}),
|
382
|
-
resolveId(id) {
|
383
|
-
if (id === runtimePublicPath) {
|
384
|
-
return id
|
385
|
-
}
|
386
|
-
},
|
387
|
-
load(id) {
|
388
|
-
if (id === runtimePublicPath) {
|
389
|
-
return runtimeCode
|
390
|
-
}
|
391
|
-
},
|
392
|
-
transformIndexHtml() {
|
393
|
-
if (!skipFastRefresh)
|
394
|
-
return [
|
395
|
-
{
|
396
|
-
tag: 'script',
|
397
|
-
attrs: { type: 'module' },
|
398
|
-
children: preambleCode.replace(`__BASE__`, devBase)
|
399
|
-
}
|
400
|
-
]
|
401
|
-
}
|
402
|
-
}
|
403
|
-
|
404
|
-
const reactJsxRuntimeId = 'react/jsx-runtime'
|
405
|
-
const reactJsxDevRuntimeId = 'react/jsx-dev-runtime'
|
406
|
-
const virtualReactJsxRuntimeId = '\0' + reactJsxRuntimeId
|
407
|
-
const virtualReactJsxDevRuntimeId = '\0' + reactJsxDevRuntimeId
|
408
|
-
// Adapted from https://github.com/alloc/vite-react-jsx
|
409
|
-
const viteReactJsx: Plugin = {
|
410
|
-
name: 'vite:react-jsx',
|
411
|
-
enforce: 'pre',
|
412
|
-
config() {
|
413
|
-
return {
|
414
|
-
optimizeDeps: {
|
415
|
-
// We can't add `react-dom` because the dependency is `react-dom/client`
|
416
|
-
// for React 18 while it's `react-dom` for React 17. We'd need to detect
|
417
|
-
// what React version the user has installed.
|
418
|
-
include: [reactJsxRuntimeId, reactJsxDevRuntimeId, 'react']
|
419
|
-
}
|
420
|
-
}
|
421
|
-
},
|
422
|
-
resolveId(id, importer) {
|
423
|
-
// Resolve runtime to a virtual path to be interoped.
|
424
|
-
// Since the interop code re-imports `id`, we need to prevent re-resolving
|
425
|
-
// to the virtual id if the importer is already the virtual id.
|
426
|
-
if (id === reactJsxRuntimeId && importer !== virtualReactJsxRuntimeId) {
|
427
|
-
return virtualReactJsxRuntimeId
|
428
|
-
}
|
429
|
-
if (
|
430
|
-
id === reactJsxDevRuntimeId &&
|
431
|
-
importer !== virtualReactJsxDevRuntimeId
|
432
|
-
) {
|
433
|
-
return virtualReactJsxDevRuntimeId
|
434
|
-
}
|
435
|
-
},
|
436
|
-
load(id) {
|
437
|
-
// Apply manual interop
|
438
|
-
if (id === virtualReactJsxRuntimeId) {
|
439
|
-
return [
|
440
|
-
`import * as jsxRuntime from ${JSON.stringify(reactJsxRuntimeId)}`,
|
441
|
-
`export const Fragment = jsxRuntime.Fragment`,
|
442
|
-
`export const jsx = jsxRuntime.jsx`,
|
443
|
-
`export const jsxs = jsxRuntime.jsxs`
|
444
|
-
].join('\n')
|
445
|
-
}
|
446
|
-
if (id === virtualReactJsxDevRuntimeId) {
|
447
|
-
return [
|
448
|
-
`import * as jsxRuntime from ${JSON.stringify(reactJsxDevRuntimeId)}`,
|
449
|
-
`export const Fragment = jsxRuntime.Fragment`,
|
450
|
-
`export const jsxDEV = jsxRuntime.jsxDEV`
|
451
|
-
].join('\n')
|
452
|
-
}
|
453
|
-
}
|
454
|
-
}
|
455
|
-
|
456
|
-
return [viteBabel, viteReactRefresh, useAutomaticRuntime && viteReactJsx]
|
457
|
-
}
|
458
|
-
|
459
|
-
viteReact.preambleCode = preambleCode
|
460
|
-
|
461
|
-
function loadPlugin(path: string): Promise<any> {
|
462
|
-
return import(path).then((module) => module.default || module)
|
463
|
-
}
|
464
|
-
|
465
|
-
function createBabelOptions(rawOptions?: BabelOptions) {
|
466
|
-
const babelOptions = {
|
467
|
-
babelrc: false,
|
468
|
-
configFile: false,
|
469
|
-
...rawOptions
|
470
|
-
} as ReactBabelOptions
|
471
|
-
|
472
|
-
babelOptions.plugins ||= []
|
473
|
-
babelOptions.presets ||= []
|
474
|
-
babelOptions.overrides ||= []
|
475
|
-
babelOptions.parserOpts ||= {} as any
|
476
|
-
babelOptions.parserOpts.plugins ||= []
|
477
|
-
|
478
|
-
return babelOptions
|
479
|
-
}
|
@@ -1,35 +0,0 @@
|
|
1
|
-
import type * as babelCore from '@babel/core'
|
2
|
-
|
3
|
-
/**
|
4
|
-
* Replace this:
|
5
|
-
*
|
6
|
-
* import { jsx as _jsx } from "react/jsx-runtime"
|
7
|
-
*
|
8
|
-
* with this:
|
9
|
-
*
|
10
|
-
* var _jsx = require("react/jsx-runtime").jsx
|
11
|
-
*/
|
12
|
-
export function babelImportToRequire({ types: t }: typeof babelCore): {
|
13
|
-
visitor: babelCore.Visitor
|
14
|
-
} {
|
15
|
-
return {
|
16
|
-
visitor: {
|
17
|
-
ImportDeclaration(path) {
|
18
|
-
const decl = path.node
|
19
|
-
const spec = decl.specifiers[0] as babelCore.types.ImportSpecifier
|
20
|
-
|
21
|
-
path.replaceWith(
|
22
|
-
t.variableDeclaration('var', [
|
23
|
-
t.variableDeclarator(
|
24
|
-
spec.local,
|
25
|
-
t.memberExpression(
|
26
|
-
t.callExpression(t.identifier('require'), [decl.source]),
|
27
|
-
spec.imported
|
28
|
-
)
|
29
|
-
)
|
30
|
-
])
|
31
|
-
)
|
32
|
-
}
|
33
|
-
}
|
34
|
-
}
|
35
|
-
}
|
@@ -1,132 +0,0 @@
|
|
1
|
-
import * as babel from '@babel/core'
|
2
|
-
import { describe, expect, it } from 'vitest'
|
3
|
-
import babelRestoreJSX from './babel-restore-jsx'
|
4
|
-
|
5
|
-
function jsx(code: string) {
|
6
|
-
return babel.transform(code, {
|
7
|
-
parserOpts: { plugins: ['jsx'] },
|
8
|
-
plugins: [babelRestoreJSX]
|
9
|
-
})?.code
|
10
|
-
}
|
11
|
-
|
12
|
-
// Tests adapted from: https://github.com/flying-sheep/babel-plugin-transform-react-createelement-to-jsx/blob/63137b6/test/index.js
|
13
|
-
describe('babel-restore-jsx', () => {
|
14
|
-
it('should convert 1-argument calls', () => {
|
15
|
-
expect(jsx('React.createElement("h1")')).toMatchInlineSnapshot(`"<h1 />;"`)
|
16
|
-
expect(jsx('React.createElement(Foo)')).toMatchInlineSnapshot(`"<Foo />;"`)
|
17
|
-
expect(jsx('React.createElement(Foo.Bar)')).toMatchInlineSnapshot(
|
18
|
-
`"<Foo.Bar />;"`
|
19
|
-
)
|
20
|
-
expect(jsx('React.createElement(Foo.Bar.Baz)')).toMatchInlineSnapshot(
|
21
|
-
`"<Foo.Bar.Baz />;"`
|
22
|
-
)
|
23
|
-
})
|
24
|
-
|
25
|
-
it('should convert effective 1-argument calls (with null or undefined)', () => {
|
26
|
-
expect(jsx('React.createElement("h1", null)')).toMatchInlineSnapshot(
|
27
|
-
`"<h1 />;"`
|
28
|
-
)
|
29
|
-
expect(jsx('React.createElement("h2", null, null)')).toMatchInlineSnapshot(
|
30
|
-
`"<h2 />;"`
|
31
|
-
)
|
32
|
-
expect(jsx('React.createElement("h3", undefined)')).toMatchInlineSnapshot(
|
33
|
-
`"<h3 />;"`
|
34
|
-
)
|
35
|
-
})
|
36
|
-
|
37
|
-
it('should handle props without children', () => {
|
38
|
-
expect(jsx('React.createElement("h1", {hi: there})')).toMatchInlineSnapshot(
|
39
|
-
`"<h1 hi={there} />;"`
|
40
|
-
)
|
41
|
-
expect(
|
42
|
-
jsx('React.createElement("h2", {"hi": there})')
|
43
|
-
).toMatchInlineSnapshot(`"<h2 hi={there} />;"`)
|
44
|
-
expect(
|
45
|
-
jsx('React.createElement("h3", {hi: "there"})')
|
46
|
-
).toMatchInlineSnapshot(`"<h3 hi=\\"there\\" />;"`)
|
47
|
-
})
|
48
|
-
|
49
|
-
it('should handle spread props', () => {
|
50
|
-
expect(jsx('React.createElement("h1", props)')).toMatchInlineSnapshot(
|
51
|
-
`"<h1 {...props} />;"`
|
52
|
-
)
|
53
|
-
expect(jsx('React.createElement("h1", getProps())')).toMatchInlineSnapshot(
|
54
|
-
`"<h1 {...getProps()} />;"`
|
55
|
-
)
|
56
|
-
})
|
57
|
-
|
58
|
-
it('should handle mixed props', () => {
|
59
|
-
expect(
|
60
|
-
jsx('React.createElement("h1", _extends({ hi: "there" }, props))')
|
61
|
-
).toMatchInlineSnapshot(`"<h1 hi=\\"there\\" {...props} />;"`)
|
62
|
-
expect(
|
63
|
-
jsx('React.createElement("h1", _extends({}, props, { hi: "there" }))')
|
64
|
-
).toMatchInlineSnapshot(`"<h1 {...props} hi=\\"there\\" />;"`)
|
65
|
-
expect(
|
66
|
-
jsx('React.createElement("h1", { ...props, hi: "there" })')
|
67
|
-
).toMatchInlineSnapshot(`"<h1 {...props} hi=\\"there\\" />;"`)
|
68
|
-
})
|
69
|
-
|
70
|
-
it('should handle props and ignore “null”/“undefined” children', () => {
|
71
|
-
expect(
|
72
|
-
jsx('React.createElement("h1", {hi: there}, null, undefined)')
|
73
|
-
).toMatchInlineSnapshot(`"<h1 hi={there} />;"`)
|
74
|
-
})
|
75
|
-
|
76
|
-
it('should ignore “null”/“undefined” props and handle children', () => {
|
77
|
-
expect(
|
78
|
-
jsx('React.createElement("h1", null, "Header")')
|
79
|
-
).toMatchInlineSnapshot(`"<h1>Header</h1>;"`)
|
80
|
-
//this can be created from e.g. '<h2>Header{"harhar"}</h2>', but i think there’s no downside to merging it
|
81
|
-
expect(
|
82
|
-
jsx('React.createElement("h2", null, "Header", "harhar")')
|
83
|
-
).toMatchInlineSnapshot(`"<h2>Headerharhar</h2>;"`)
|
84
|
-
expect(
|
85
|
-
jsx('React.createElement("h3", null, React.createElement("i"))')
|
86
|
-
).toMatchInlineSnapshot(`"<h3><i /></h3>;"`)
|
87
|
-
expect(
|
88
|
-
jsx('React.createElement("h4", null, "a", React.createElement("b"), "c")')
|
89
|
-
).toMatchInlineSnapshot(`"<h4>a<b />c</h4>;"`)
|
90
|
-
})
|
91
|
-
|
92
|
-
it('should handle props and children', () => {
|
93
|
-
//we extensively tested props and children separately, so only sth. basic
|
94
|
-
expect(
|
95
|
-
jsx('React.createElement("h1", {hi: there}, "Header")')
|
96
|
-
).toMatchInlineSnapshot(`"<h1 hi={there}>Header</h1>;"`)
|
97
|
-
})
|
98
|
-
|
99
|
-
it('should ignore intermingled “null”/“undefined” children', () => {
|
100
|
-
expect(
|
101
|
-
jsx('React.createElement("h1", null, null, "Header", undefined)')
|
102
|
-
).toMatchInlineSnapshot(`"<h1>Header</h1>;"`)
|
103
|
-
})
|
104
|
-
|
105
|
-
it('should handle children in nested expressions', () => {
|
106
|
-
expect(
|
107
|
-
jsx(
|
108
|
-
'React.createElement("h1", null, foo ? React.createElement("p") : null)'
|
109
|
-
)
|
110
|
-
).toMatchInlineSnapshot(`"<h1>{foo ? <p /> : null}</h1>;"`)
|
111
|
-
})
|
112
|
-
|
113
|
-
it('should handle lowercase component names', () => {
|
114
|
-
expect(jsx('React.createElement(aaa)')).toMatchInlineSnapshot(
|
115
|
-
`"React.createElement(aaa);"`
|
116
|
-
)
|
117
|
-
})
|
118
|
-
|
119
|
-
it('should not handle contains __self prop', () => {
|
120
|
-
expect(
|
121
|
-
jsx('React.createElement(Provider, { __self: this })')
|
122
|
-
).toMatchInlineSnapshot('"<Provider />;"')
|
123
|
-
})
|
124
|
-
|
125
|
-
it('should not handle contains __source prop', () => {
|
126
|
-
expect(
|
127
|
-
jsx(
|
128
|
-
'React.createElement(Provider, { __source: { fileName: _jsxFileName, lineNumber: 133 }})'
|
129
|
-
)
|
130
|
-
).toMatchInlineSnapshot('"<Provider />;"')
|
131
|
-
})
|
132
|
-
})
|
@@ -1,232 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* https://github.com/flying-sheep/babel-plugin-transform-react-createelement-to-jsx
|
3
|
-
* @license GNU General Public License v3.0
|
4
|
-
*/
|
5
|
-
import type * as babel from '@babel/core'
|
6
|
-
|
7
|
-
interface PluginOptions {
|
8
|
-
reactAlias: string
|
9
|
-
}
|
10
|
-
|
11
|
-
/**
|
12
|
-
* Visitor factory for babel, converting React.createElement(...) to <jsx ...>...</jsx>
|
13
|
-
*
|
14
|
-
* What we want to handle here is this CallExpression:
|
15
|
-
*
|
16
|
-
* React.createElement(
|
17
|
-
* type: StringLiteral|Identifier|MemberExpression,
|
18
|
-
* [props: ObjectExpression|Expression],
|
19
|
-
* [...children: StringLiteral|Expression]
|
20
|
-
* )
|
21
|
-
*
|
22
|
-
* Any of those arguments might also be missing (undefined) and/or invalid.
|
23
|
-
*/
|
24
|
-
export default function (
|
25
|
-
{ types: t }: typeof babel,
|
26
|
-
{ reactAlias = 'React' }: PluginOptions
|
27
|
-
): babel.PluginObj {
|
28
|
-
/**
|
29
|
-
* Get a `JSXElement` from a `CallExpression`.
|
30
|
-
* Returns `null` if this impossible.
|
31
|
-
*/
|
32
|
-
function getJSXNode(node: any): any {
|
33
|
-
if (!isReactCreateElement(node)) {
|
34
|
-
return null
|
35
|
-
}
|
36
|
-
|
37
|
-
//nameNode and propsNode may be undefined, getJSX* need to handle that
|
38
|
-
const [nameNode, propsNode, ...childNodes] = node.arguments
|
39
|
-
|
40
|
-
const name = getJSXName(nameNode)
|
41
|
-
if (name == null) {
|
42
|
-
return null //name is required
|
43
|
-
}
|
44
|
-
|
45
|
-
const props = getJSXProps(propsNode)
|
46
|
-
if (props == null) {
|
47
|
-
return null //no props → [], invalid → null
|
48
|
-
}
|
49
|
-
|
50
|
-
const children = getJSXChildren(childNodes)
|
51
|
-
if (children == null) {
|
52
|
-
return null //no children → [], invalid → null
|
53
|
-
}
|
54
|
-
|
55
|
-
if (
|
56
|
-
t.isJSXMemberExpression(name) &&
|
57
|
-
t.isJSXIdentifier(name.object) &&
|
58
|
-
name.object.name === reactAlias &&
|
59
|
-
name.property.name === 'Fragment'
|
60
|
-
) {
|
61
|
-
return t.jsxFragment(
|
62
|
-
t.jsxOpeningFragment(),
|
63
|
-
t.jsxClosingFragment(),
|
64
|
-
children
|
65
|
-
)
|
66
|
-
}
|
67
|
-
|
68
|
-
// self-closing tag if no children
|
69
|
-
const selfClosing = children.length === 0
|
70
|
-
const startTag = t.jsxOpeningElement(name, props, selfClosing)
|
71
|
-
startTag.loc = node.loc
|
72
|
-
const endTag = selfClosing ? null : t.jsxClosingElement(name)
|
73
|
-
|
74
|
-
return t.jsxElement(startTag, endTag, children, selfClosing)
|
75
|
-
}
|
76
|
-
|
77
|
-
/**
|
78
|
-
* Get a JSXIdentifier or JSXMemberExpression from a Node of known type.
|
79
|
-
* Returns null if an unknown node type, null or undefined is passed.
|
80
|
-
*/
|
81
|
-
function getJSXName(node: any): any {
|
82
|
-
if (node == null) {
|
83
|
-
return null
|
84
|
-
}
|
85
|
-
|
86
|
-
const name = getJSXIdentifier(node, true)
|
87
|
-
if (name != null) {
|
88
|
-
return name
|
89
|
-
}
|
90
|
-
|
91
|
-
if (!t.isMemberExpression(node)) {
|
92
|
-
return null
|
93
|
-
}
|
94
|
-
const object = getJSXName(node.object)
|
95
|
-
const property = getJSXName(node.property)
|
96
|
-
if (object == null || property == null) {
|
97
|
-
return null
|
98
|
-
}
|
99
|
-
return t.jsxMemberExpression(object, property)
|
100
|
-
}
|
101
|
-
|
102
|
-
/**
|
103
|
-
* Get an array of JSX(Spread)Attribute from a props ObjectExpression.
|
104
|
-
* Handles the _extends Expression babel creates from SpreadElement nodes.
|
105
|
-
* Returns null if a validation error occurs.
|
106
|
-
*/
|
107
|
-
function getJSXProps(node: any): any[] | null {
|
108
|
-
if (node == null || isNullLikeNode(node)) {
|
109
|
-
return []
|
110
|
-
}
|
111
|
-
|
112
|
-
if (
|
113
|
-
t.isCallExpression(node) &&
|
114
|
-
t.isIdentifier(node.callee, { name: '_extends' })
|
115
|
-
) {
|
116
|
-
const props: any[] = node.arguments.map(getJSXProps)
|
117
|
-
//if calling this recursively works, flatten.
|
118
|
-
if (props.every((prop) => prop != null)) {
|
119
|
-
return [].concat(...props)
|
120
|
-
}
|
121
|
-
}
|
122
|
-
|
123
|
-
if (!t.isObjectExpression(node) && t.isExpression(node))
|
124
|
-
return [t.jsxSpreadAttribute(node)]
|
125
|
-
|
126
|
-
if (!isPlainObjectExpression(node)) {
|
127
|
-
return null
|
128
|
-
}
|
129
|
-
return node.properties
|
130
|
-
.map((prop: any) =>
|
131
|
-
t.isObjectProperty(prop)
|
132
|
-
? t.jsxAttribute(
|
133
|
-
getJSXIdentifier(prop.key)!,
|
134
|
-
getJSXAttributeValue(prop.value)
|
135
|
-
)
|
136
|
-
: t.jsxSpreadAttribute(prop.argument)
|
137
|
-
)
|
138
|
-
.filter((prop: any) =>
|
139
|
-
t.isJSXIdentifier(prop.name)
|
140
|
-
? prop.name.name !== '__self' && prop.name.name !== '__source'
|
141
|
-
: true
|
142
|
-
)
|
143
|
-
}
|
144
|
-
|
145
|
-
function getJSXChild(node: any) {
|
146
|
-
if (t.isStringLiteral(node)) {
|
147
|
-
return t.jsxText(node.value)
|
148
|
-
}
|
149
|
-
if (isReactCreateElement(node)) {
|
150
|
-
return getJSXNode(node)
|
151
|
-
}
|
152
|
-
if (t.isExpression(node)) {
|
153
|
-
return t.jsxExpressionContainer(node)
|
154
|
-
}
|
155
|
-
return null
|
156
|
-
}
|
157
|
-
|
158
|
-
function getJSXChildren(nodes: any[]) {
|
159
|
-
const children = nodes
|
160
|
-
.filter((node) => !isNullLikeNode(node))
|
161
|
-
.map(getJSXChild)
|
162
|
-
if (children.some((child) => child == null)) {
|
163
|
-
return null
|
164
|
-
}
|
165
|
-
return children
|
166
|
-
}
|
167
|
-
|
168
|
-
function getJSXIdentifier(node: any, tag = false) {
|
169
|
-
//TODO: JSXNamespacedName
|
170
|
-
if (t.isIdentifier(node) && (!tag || node.name.match(/^[A-Z]/))) {
|
171
|
-
return t.jsxIdentifier(node.name)
|
172
|
-
}
|
173
|
-
if (t.isStringLiteral(node)) {
|
174
|
-
return t.jsxIdentifier(node.value)
|
175
|
-
}
|
176
|
-
return null
|
177
|
-
}
|
178
|
-
|
179
|
-
function getJSXAttributeValue(node: any) {
|
180
|
-
if (t.isStringLiteral(node)) {
|
181
|
-
return node
|
182
|
-
}
|
183
|
-
if (t.isJSXElement(node)) {
|
184
|
-
return node
|
185
|
-
}
|
186
|
-
if (t.isExpression(node)) {
|
187
|
-
return t.jsxExpressionContainer(node)
|
188
|
-
}
|
189
|
-
return null
|
190
|
-
}
|
191
|
-
|
192
|
-
/**
|
193
|
-
* Tests if a node is a CallExpression with callee `React.createElement`
|
194
|
-
*/
|
195
|
-
const isReactCreateElement = (node: any) =>
|
196
|
-
t.isCallExpression(node) &&
|
197
|
-
t.isMemberExpression(node.callee) &&
|
198
|
-
t.isIdentifier(node.callee.object, { name: reactAlias }) &&
|
199
|
-
t.isIdentifier(node.callee.property, { name: 'createElement' }) &&
|
200
|
-
!node.callee.computed
|
201
|
-
|
202
|
-
/**
|
203
|
-
* Tests if a node is `null` or `undefined`
|
204
|
-
*/
|
205
|
-
const isNullLikeNode = (node: any) =>
|
206
|
-
t.isNullLiteral(node) || t.isIdentifier(node, { name: 'undefined' })
|
207
|
-
|
208
|
-
/**
|
209
|
-
* Tests if a node is an object expression with noncomputed, nonmethod attrs
|
210
|
-
*/
|
211
|
-
const isPlainObjectExpression = (node: any) =>
|
212
|
-
t.isObjectExpression(node) &&
|
213
|
-
node.properties.every(
|
214
|
-
(property) =>
|
215
|
-
t.isSpreadElement(property) ||
|
216
|
-
(t.isObjectProperty(property, { computed: false }) &&
|
217
|
-
getJSXIdentifier(property.key) != null &&
|
218
|
-
getJSXAttributeValue(property.value) != null)
|
219
|
-
)
|
220
|
-
|
221
|
-
return {
|
222
|
-
visitor: {
|
223
|
-
CallExpression(path) {
|
224
|
-
const node = getJSXNode(path.node)
|
225
|
-
if (node == null) {
|
226
|
-
return null
|
227
|
-
}
|
228
|
-
path.replaceWith(node)
|
229
|
-
}
|
230
|
-
}
|
231
|
-
}
|
232
|
-
}
|
@@ -1,147 +0,0 @@
|
|
1
|
-
import * as babel from '@babel/core'
|
2
|
-
import { describe, expect, it } from 'vitest'
|
3
|
-
import { parseReactAlias, restoreJSX } from './restore-jsx'
|
4
|
-
|
5
|
-
describe('parseReactAlias', () => {
|
6
|
-
it('handles cjs require', () => {
|
7
|
-
expect(parseReactAlias(`const React = require("react")`))
|
8
|
-
.toMatchInlineSnapshot(`
|
9
|
-
[
|
10
|
-
"React",
|
11
|
-
true,
|
12
|
-
]
|
13
|
-
`)
|
14
|
-
})
|
15
|
-
|
16
|
-
it('handles cjs require (minified)', () => {
|
17
|
-
expect(parseReactAlias(`var F=require('foo');var R=require('react')`))
|
18
|
-
.toMatchInlineSnapshot(`
|
19
|
-
[
|
20
|
-
"R",
|
21
|
-
true,
|
22
|
-
]
|
23
|
-
`)
|
24
|
-
})
|
25
|
-
|
26
|
-
it('does not handle destructured cjs require', () => {
|
27
|
-
expect(parseReactAlias(`var {createElement} = require("react")`))
|
28
|
-
.toMatchInlineSnapshot(`
|
29
|
-
[
|
30
|
-
undefined,
|
31
|
-
false,
|
32
|
-
]
|
33
|
-
`)
|
34
|
-
})
|
35
|
-
|
36
|
-
it('handles esm import', () => {
|
37
|
-
expect(parseReactAlias(`import React from 'react'`)).toMatchInlineSnapshot(`
|
38
|
-
[
|
39
|
-
"React",
|
40
|
-
false,
|
41
|
-
]
|
42
|
-
`)
|
43
|
-
})
|
44
|
-
|
45
|
-
it('handles esm import namespace', () => {
|
46
|
-
expect(parseReactAlias(`import * as React from "react"`))
|
47
|
-
.toMatchInlineSnapshot(`
|
48
|
-
[
|
49
|
-
"React",
|
50
|
-
false,
|
51
|
-
]
|
52
|
-
`)
|
53
|
-
})
|
54
|
-
|
55
|
-
it('does not handle destructured esm import', () => {
|
56
|
-
expect(parseReactAlias(`import {createElement} from "react"`))
|
57
|
-
.toMatchInlineSnapshot(`
|
58
|
-
[
|
59
|
-
undefined,
|
60
|
-
false,
|
61
|
-
]
|
62
|
-
`)
|
63
|
-
})
|
64
|
-
})
|
65
|
-
|
66
|
-
async function jsx(sourceCode: string) {
|
67
|
-
const [ast] = await restoreJSX(babel, sourceCode, 'test.js')
|
68
|
-
if (ast == null) {
|
69
|
-
return ast
|
70
|
-
}
|
71
|
-
const { code } = await babel.transformFromAstAsync(ast, null, {
|
72
|
-
configFile: false
|
73
|
-
})
|
74
|
-
return code
|
75
|
-
}
|
76
|
-
// jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
77
|
-
// React__default.createElement(Foo)`)
|
78
|
-
// Tests adapted from: https://github.com/flying-sheep/babel-plugin-transform-react-createelement-to-jsx/blob/63137b6/test/index.js
|
79
|
-
describe('restore-jsx', () => {
|
80
|
-
it('should trans to ', async () => {
|
81
|
-
expect(
|
82
|
-
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
83
|
-
React__default.createElement(foo)`)
|
84
|
-
).toMatchInlineSnapshot(`
|
85
|
-
"import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
86
|
-
React__default.createElement(foo);"
|
87
|
-
`)
|
88
|
-
expect(
|
89
|
-
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
90
|
-
React__default.createElement("h1")`)
|
91
|
-
).toMatch(`<h1 />;`)
|
92
|
-
expect(
|
93
|
-
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
94
|
-
React__default.createElement(Foo)`)
|
95
|
-
).toMatch(`<Foo />;`)
|
96
|
-
expect(
|
97
|
-
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
98
|
-
React__default.createElement(Foo.Bar)`)
|
99
|
-
).toMatch(`<Foo.Bar />;`)
|
100
|
-
expect(
|
101
|
-
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
102
|
-
React__default.createElement(Foo.Bar.Baz)`)
|
103
|
-
).toMatch(`<Foo.Bar.Baz />;`)
|
104
|
-
})
|
105
|
-
|
106
|
-
it('should handle props', async () => {
|
107
|
-
expect(
|
108
|
-
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
109
|
-
React__default.createElement(foo, {hi: there})`)
|
110
|
-
).toMatchInlineSnapshot(`
|
111
|
-
"import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
112
|
-
React__default.createElement(foo, {
|
113
|
-
hi: there
|
114
|
-
});"
|
115
|
-
`)
|
116
|
-
expect(
|
117
|
-
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
118
|
-
React__default.createElement("h1", {hi: there})`)
|
119
|
-
).toMatch(`<h1 hi={there} />;`)
|
120
|
-
expect(
|
121
|
-
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
122
|
-
React__default.createElement(Foo, {hi: there})`)
|
123
|
-
).toMatch(`<Foo hi={there} />;`)
|
124
|
-
})
|
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
|
-
})
|
147
|
-
})
|
@@ -1,74 +0,0 @@
|
|
1
|
-
import type * as babelCore from '@babel/core'
|
2
|
-
|
3
|
-
type RestoredJSX = [
|
4
|
-
result: babelCore.types.File | null | undefined,
|
5
|
-
isCommonJS: boolean
|
6
|
-
]
|
7
|
-
|
8
|
-
let babelRestoreJSX: Promise<babelCore.PluginItem> | undefined
|
9
|
-
|
10
|
-
const jsxNotFound: RestoredJSX = [null, false]
|
11
|
-
|
12
|
-
async function getBabelRestoreJSX() {
|
13
|
-
if (!babelRestoreJSX)
|
14
|
-
babelRestoreJSX = import('./babel-restore-jsx').then((r) => {
|
15
|
-
const fn = r.default
|
16
|
-
if ('default' in fn)
|
17
|
-
// @ts-expect-error
|
18
|
-
return fn.default
|
19
|
-
return fn
|
20
|
-
})
|
21
|
-
return babelRestoreJSX
|
22
|
-
}
|
23
|
-
|
24
|
-
/** Restore JSX from `React.createElement` calls */
|
25
|
-
export async function restoreJSX(
|
26
|
-
babel: typeof babelCore,
|
27
|
-
code: string,
|
28
|
-
filename: string
|
29
|
-
): Promise<RestoredJSX> {
|
30
|
-
const [reactAlias, isCommonJS] = parseReactAlias(code)
|
31
|
-
|
32
|
-
if (!reactAlias) {
|
33
|
-
return jsxNotFound
|
34
|
-
}
|
35
|
-
|
36
|
-
const reactJsxRE = new RegExp(
|
37
|
-
`\\b${reactAlias}\\.(createElement|Fragment)\\b`,
|
38
|
-
'g'
|
39
|
-
)
|
40
|
-
|
41
|
-
if (!reactJsxRE.test(code)) {
|
42
|
-
return jsxNotFound
|
43
|
-
}
|
44
|
-
|
45
|
-
const result = await babel.transformAsync(code, {
|
46
|
-
babelrc: false,
|
47
|
-
configFile: false,
|
48
|
-
ast: true,
|
49
|
-
code: false,
|
50
|
-
filename,
|
51
|
-
parserOpts: {
|
52
|
-
plugins: ['jsx']
|
53
|
-
},
|
54
|
-
plugins: [[await getBabelRestoreJSX(), { reactAlias }]]
|
55
|
-
})
|
56
|
-
|
57
|
-
return [result?.ast, isCommonJS]
|
58
|
-
}
|
59
|
-
|
60
|
-
export function parseReactAlias(
|
61
|
-
code: string
|
62
|
-
): [alias: string | undefined, isCommonJS: boolean] {
|
63
|
-
let match = code.match(
|
64
|
-
/\b(var|let|const)\s+([^=\{\s]+)\s*=\s*require\(["']react["']\)/
|
65
|
-
)
|
66
|
-
if (match) {
|
67
|
-
return [match[2], true]
|
68
|
-
}
|
69
|
-
match = code.match(/^import\s+(?:\*\s+as\s+)?(\w+).+?\bfrom\s*["']react["']/m)
|
70
|
-
if (match) {
|
71
|
-
return [match[1], false]
|
72
|
-
}
|
73
|
-
return [undefined, false]
|
74
|
-
}
|