@vitejs/plugin-react 2.0.0-beta.0 → 2.0.1
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/README.md +1 -1
- package/dist/chunks/babel-restore-jsx.cjs +17 -6
- package/dist/chunks/babel-restore-jsx.mjs +17 -6
- package/dist/index.cjs +71 -32
- package/dist/index.mjs +70 -32
- package/package.json +10 -9
- package/src/fast-refresh.ts +1 -0
- package/src/index.ts +29 -5
- package/src/jsx-runtime/babel-restore-jsx.ts +10 -3
- package/src/jsx-runtime/restore-jsx.spec.ts +32 -2
- package/src/jsx-runtime/restore-jsx.ts +6 -28
package/README.md
CHANGED
@@ -83,7 +83,7 @@ Here's the [complete list of Babel parser plugins](https://babeljs.io/docs/en/ba
|
|
83
83
|
|
84
84
|
## Middleware mode
|
85
85
|
|
86
|
-
In [middleware mode](https://vitejs.dev/config
|
86
|
+
In [middleware mode](https://vitejs.dev/config/server-options.html#server-middlewaremode), you should make sure your entry `index.html` file is transformed by Vite. Here's an example for an Express server:
|
87
87
|
|
88
88
|
```js
|
89
89
|
app.get('/', async (req, res, next) => {
|
@@ -4,7 +4,7 @@
|
|
4
4
|
* https://github.com/flying-sheep/babel-plugin-transform-react-createelement-to-jsx
|
5
5
|
* @license GNU General Public License v3.0
|
6
6
|
*/
|
7
|
-
function babelRestoreJsx({ types: t }) {
|
7
|
+
function babelRestoreJsx({ types: t }, { reactAlias = "React" }) {
|
8
8
|
function getJSXNode(node) {
|
9
9
|
if (!isReactCreateElement(node)) {
|
10
10
|
return null;
|
@@ -22,8 +22,12 @@ function babelRestoreJsx({ types: t }) {
|
|
22
22
|
if (children == null) {
|
23
23
|
return null;
|
24
24
|
}
|
25
|
-
if (t.isJSXMemberExpression(name) && t.isJSXIdentifier(name.object) && name.object.name ===
|
26
|
-
return t.jsxFragment(
|
25
|
+
if (t.isJSXMemberExpression(name) && t.isJSXIdentifier(name.object) && name.object.name === reactAlias && name.property.name === "Fragment") {
|
26
|
+
return t.jsxFragment(
|
27
|
+
t.jsxOpeningFragment(),
|
28
|
+
t.jsxClosingFragment(),
|
29
|
+
children
|
30
|
+
);
|
27
31
|
}
|
28
32
|
const selfClosing = children.length === 0;
|
29
33
|
const startTag = t.jsxOpeningElement(name, props, selfClosing);
|
@@ -64,7 +68,12 @@ function babelRestoreJsx({ types: t }) {
|
|
64
68
|
if (!isPlainObjectExpression(node)) {
|
65
69
|
return null;
|
66
70
|
}
|
67
|
-
return node.properties.map(
|
71
|
+
return node.properties.map(
|
72
|
+
(prop) => t.isObjectProperty(prop) ? t.jsxAttribute(
|
73
|
+
getJSXIdentifier(prop.key),
|
74
|
+
getJSXAttributeValue(prop.value)
|
75
|
+
) : t.jsxSpreadAttribute(prop.argument)
|
76
|
+
);
|
68
77
|
}
|
69
78
|
function getJSXChild(node) {
|
70
79
|
if (t.isStringLiteral(node)) {
|
@@ -106,9 +115,11 @@ function babelRestoreJsx({ types: t }) {
|
|
106
115
|
}
|
107
116
|
return null;
|
108
117
|
}
|
109
|
-
const isReactCreateElement = (node) => t.isCallExpression(node) && t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object, { name:
|
118
|
+
const isReactCreateElement = (node) => t.isCallExpression(node) && t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object, { name: reactAlias }) && t.isIdentifier(node.callee.property, { name: "createElement" }) && !node.callee.computed;
|
110
119
|
const isNullLikeNode = (node) => t.isNullLiteral(node) || t.isIdentifier(node, { name: "undefined" });
|
111
|
-
const isPlainObjectExpression = (node) => t.isObjectExpression(node) && node.properties.every(
|
120
|
+
const isPlainObjectExpression = (node) => t.isObjectExpression(node) && node.properties.every(
|
121
|
+
(property) => t.isSpreadElement(property) || t.isObjectProperty(property, { computed: false }) && getJSXIdentifier(property.key) != null && getJSXAttributeValue(property.value) != null
|
122
|
+
);
|
112
123
|
return {
|
113
124
|
visitor: {
|
114
125
|
CallExpression(path) {
|
@@ -2,7 +2,7 @@
|
|
2
2
|
* https://github.com/flying-sheep/babel-plugin-transform-react-createelement-to-jsx
|
3
3
|
* @license GNU General Public License v3.0
|
4
4
|
*/
|
5
|
-
function babelRestoreJsx({ types: t }) {
|
5
|
+
function babelRestoreJsx({ types: t }, { reactAlias = "React" }) {
|
6
6
|
function getJSXNode(node) {
|
7
7
|
if (!isReactCreateElement(node)) {
|
8
8
|
return null;
|
@@ -20,8 +20,12 @@ function babelRestoreJsx({ types: t }) {
|
|
20
20
|
if (children == null) {
|
21
21
|
return null;
|
22
22
|
}
|
23
|
-
if (t.isJSXMemberExpression(name) && t.isJSXIdentifier(name.object) && name.object.name ===
|
24
|
-
return t.jsxFragment(
|
23
|
+
if (t.isJSXMemberExpression(name) && t.isJSXIdentifier(name.object) && name.object.name === reactAlias && name.property.name === "Fragment") {
|
24
|
+
return t.jsxFragment(
|
25
|
+
t.jsxOpeningFragment(),
|
26
|
+
t.jsxClosingFragment(),
|
27
|
+
children
|
28
|
+
);
|
25
29
|
}
|
26
30
|
const selfClosing = children.length === 0;
|
27
31
|
const startTag = t.jsxOpeningElement(name, props, selfClosing);
|
@@ -62,7 +66,12 @@ function babelRestoreJsx({ types: t }) {
|
|
62
66
|
if (!isPlainObjectExpression(node)) {
|
63
67
|
return null;
|
64
68
|
}
|
65
|
-
return node.properties.map(
|
69
|
+
return node.properties.map(
|
70
|
+
(prop) => t.isObjectProperty(prop) ? t.jsxAttribute(
|
71
|
+
getJSXIdentifier(prop.key),
|
72
|
+
getJSXAttributeValue(prop.value)
|
73
|
+
) : t.jsxSpreadAttribute(prop.argument)
|
74
|
+
);
|
66
75
|
}
|
67
76
|
function getJSXChild(node) {
|
68
77
|
if (t.isStringLiteral(node)) {
|
@@ -104,9 +113,11 @@ function babelRestoreJsx({ types: t }) {
|
|
104
113
|
}
|
105
114
|
return null;
|
106
115
|
}
|
107
|
-
const isReactCreateElement = (node) => t.isCallExpression(node) && t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object, { name:
|
116
|
+
const isReactCreateElement = (node) => t.isCallExpression(node) && t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object, { name: reactAlias }) && t.isIdentifier(node.callee.property, { name: "createElement" }) && !node.callee.computed;
|
108
117
|
const isNullLikeNode = (node) => t.isNullLiteral(node) || t.isIdentifier(node, { name: "undefined" });
|
109
|
-
const isPlainObjectExpression = (node) => t.isObjectExpression(node) && node.properties.every(
|
118
|
+
const isPlainObjectExpression = (node) => t.isObjectExpression(node) && node.properties.every(
|
119
|
+
(property) => t.isSpreadElement(property) || t.isObjectProperty(property, { computed: false }) && getJSXIdentifier(property.key) != null && getJSXAttributeValue(property.value) != null
|
120
|
+
);
|
110
121
|
return {
|
111
122
|
visitor: {
|
112
123
|
CallExpression(path) {
|
package/dist/index.cjs
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
const path = require('node:path');
|
4
4
|
const babel = require('@babel/core');
|
5
5
|
const vite = require('vite');
|
6
|
+
const MagicString = require('magic-string');
|
6
7
|
const fs = require('node:fs');
|
7
8
|
const node_module = require('node:module');
|
8
9
|
|
@@ -22,12 +23,18 @@ function _interopNamespace(e) {
|
|
22
23
|
|
23
24
|
const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
24
25
|
const babel__namespace = /*#__PURE__*/_interopNamespace(babel);
|
26
|
+
const MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString);
|
25
27
|
const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
26
28
|
|
27
29
|
const runtimePublicPath = "/@react-refresh";
|
28
30
|
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)));
|
29
|
-
const reactRefreshDir = path__default.dirname(
|
30
|
-
|
31
|
+
const reactRefreshDir = path__default.dirname(
|
32
|
+
_require.resolve("react-refresh/package.json")
|
33
|
+
);
|
34
|
+
const runtimeFilePath = path__default.join(
|
35
|
+
reactRefreshDir,
|
36
|
+
"cjs/react-refresh-runtime.development.js"
|
37
|
+
);
|
31
38
|
const runtimeCode = `
|
32
39
|
const exports = {}
|
33
40
|
${fs__default.readFileSync(runtimeFilePath, "utf-8")}
|
@@ -92,8 +99,12 @@ function isRefreshBoundary(ast) {
|
|
92
99
|
}
|
93
100
|
const { declaration, specifiers } = node;
|
94
101
|
if (declaration) {
|
102
|
+
if (declaration.type === "ClassDeclaration")
|
103
|
+
return false;
|
95
104
|
if (declaration.type === "VariableDeclaration") {
|
96
|
-
return declaration.declarations.every(
|
105
|
+
return declaration.declarations.every(
|
106
|
+
(variable) => isComponentLikeIdentifier(variable.id)
|
107
|
+
);
|
97
108
|
}
|
98
109
|
if (declaration.type === "FunctionDeclaration") {
|
99
110
|
return !!declaration.id && isComponentLikeIdentifier(declaration.id);
|
@@ -117,9 +128,17 @@ function babelImportToRequire({ types: t }) {
|
|
117
128
|
ImportDeclaration(path) {
|
118
129
|
const decl = path.node;
|
119
130
|
const spec = decl.specifiers[0];
|
120
|
-
path.replaceWith(
|
121
|
-
t.
|
122
|
-
|
131
|
+
path.replaceWith(
|
132
|
+
t.variableDeclaration("var", [
|
133
|
+
t.variableDeclarator(
|
134
|
+
spec.local,
|
135
|
+
t.memberExpression(
|
136
|
+
t.callExpression(t.identifier("require"), [decl.source]),
|
137
|
+
spec.imported
|
138
|
+
)
|
139
|
+
)
|
140
|
+
])
|
141
|
+
);
|
123
142
|
}
|
124
143
|
}
|
125
144
|
};
|
@@ -142,20 +161,11 @@ async function restoreJSX(babel, code, filename) {
|
|
142
161
|
if (!reactAlias) {
|
143
162
|
return jsxNotFound;
|
144
163
|
}
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
return "React.Fragment";
|
151
|
-
}).replace(new RegExp(createElementPattern, "g"), (original, component) => {
|
152
|
-
if (/^[a-z][\w$]*$/.test(component)) {
|
153
|
-
return original;
|
154
|
-
}
|
155
|
-
hasCompiledJsx = true;
|
156
|
-
return "React.createElement(" + (component === "Fragment" ? "React.Fragment" : component);
|
157
|
-
});
|
158
|
-
if (!hasCompiledJsx) {
|
164
|
+
const reactJsxRE = new RegExp(
|
165
|
+
`\\b${reactAlias}\\.(createElement|Fragment)\\b`,
|
166
|
+
"g"
|
167
|
+
);
|
168
|
+
if (!reactJsxRE.test(code)) {
|
159
169
|
return jsxNotFound;
|
160
170
|
}
|
161
171
|
const result = await babel.transformAsync(code, {
|
@@ -167,12 +177,14 @@ async function restoreJSX(babel, code, filename) {
|
|
167
177
|
parserOpts: {
|
168
178
|
plugins: ["jsx"]
|
169
179
|
},
|
170
|
-
plugins: [await getBabelRestoreJSX()]
|
180
|
+
plugins: [[await getBabelRestoreJSX(), { reactAlias }]]
|
171
181
|
});
|
172
182
|
return [result?.ast, isCommonJS];
|
173
183
|
}
|
174
184
|
function parseReactAlias(code) {
|
175
|
-
let match = code.match(
|
185
|
+
let match = code.match(
|
186
|
+
/\b(var|let|const)\s+([^=\{\s]+)\s*=\s*require\(["']react["']\)/
|
187
|
+
);
|
176
188
|
if (match) {
|
177
189
|
return [match[2], true];
|
178
190
|
}
|
@@ -183,10 +195,12 @@ function parseReactAlias(code) {
|
|
183
195
|
return [void 0, false];
|
184
196
|
}
|
185
197
|
|
198
|
+
const prependReactImportCode = "import React from 'react'; ";
|
186
199
|
function viteReact(opts = {}) {
|
187
200
|
let devBase = "/";
|
188
201
|
let resolvedCacheDir;
|
189
202
|
let filter = vite.createFilter(opts.include, opts.exclude);
|
203
|
+
let needHiresSourcemap = false;
|
190
204
|
let isProduction = true;
|
191
205
|
let projectRoot = process.cwd();
|
192
206
|
let skipFastRefresh = opts.fastRefresh === false;
|
@@ -217,25 +231,30 @@ function viteReact(opts = {}) {
|
|
217
231
|
filter = vite.createFilter(opts.include, opts.exclude, {
|
218
232
|
resolve: projectRoot
|
219
233
|
});
|
234
|
+
needHiresSourcemap = config.command === "build" && !!config.build.sourcemap;
|
220
235
|
isProduction = config.isProduction;
|
221
236
|
skipFastRefresh || (skipFastRefresh = isProduction || config.command === "build");
|
222
237
|
const jsxInject = config.esbuild && config.esbuild.jsxInject;
|
223
238
|
if (jsxInject && importReactRE.test(jsxInject)) {
|
224
239
|
skipReactImport = true;
|
225
|
-
config.logger.warn(
|
240
|
+
config.logger.warn(
|
241
|
+
"[@vitejs/plugin-react] This plugin imports React for you automatically, so you can stop using `esbuild.jsxInject` for that purpose."
|
242
|
+
);
|
226
243
|
}
|
227
244
|
config.plugins.forEach((plugin) => {
|
228
245
|
const hasConflict = plugin.name === "react-refresh" || plugin !== viteReactJsx && plugin.name === "vite:react-jsx";
|
229
246
|
if (hasConflict)
|
230
|
-
return config.logger.warn(
|
247
|
+
return config.logger.warn(
|
248
|
+
`[@vitejs/plugin-react] You should stop using "${plugin.name}" since this plugin conflicts with it.`
|
249
|
+
);
|
231
250
|
});
|
232
251
|
runPluginOverrides = (babelOptions, context) => {
|
233
252
|
const hooks = config.plugins.map((plugin) => plugin.api?.reactBabel).filter(Boolean);
|
234
253
|
if (hooks.length > 0) {
|
235
|
-
return (runPluginOverrides = (babelOptions2) => {
|
236
|
-
hooks.forEach((hook) => hook(babelOptions2,
|
254
|
+
return (runPluginOverrides = (babelOptions2, context2) => {
|
255
|
+
hooks.forEach((hook) => hook(babelOptions2, context2, config));
|
237
256
|
return true;
|
238
|
-
})(babelOptions);
|
257
|
+
})(babelOptions, context);
|
239
258
|
}
|
240
259
|
runPluginOverrides = () => false;
|
241
260
|
return false;
|
@@ -273,13 +292,16 @@ function viteReact(opts = {}) {
|
|
273
292
|
}
|
274
293
|
}
|
275
294
|
let ast;
|
295
|
+
let prependReactImport = false;
|
276
296
|
if (!isProjectFile || isJSX) {
|
277
297
|
if (useAutomaticRuntime) {
|
278
298
|
const isOptimizedReactDom = id.startsWith(resolvedCacheDir) && id.includes("/react-dom.js");
|
279
299
|
const [restoredAst, isCommonJS] = !isProjectFile && !isJSX && !isOptimizedReactDom ? await restoreJSX(babel__namespace, code, id) : [null, false];
|
280
300
|
if (isJSX || (ast = restoredAst)) {
|
281
301
|
plugins.push([
|
282
|
-
await loadPlugin(
|
302
|
+
await loadPlugin(
|
303
|
+
"@babel/plugin-transform-react-jsx" + (isProduction ? "" : "-development")
|
304
|
+
),
|
283
305
|
{
|
284
306
|
runtime: "automatic",
|
285
307
|
importSource: opts.jsxImportSource,
|
@@ -292,16 +314,33 @@ function viteReact(opts = {}) {
|
|
292
314
|
}
|
293
315
|
} else if (isProjectFile) {
|
294
316
|
if (!isProduction) {
|
295
|
-
plugins.push(
|
317
|
+
plugins.push(
|
318
|
+
await loadPlugin("@babel/plugin-transform-react-jsx-self"),
|
319
|
+
await loadPlugin("@babel/plugin-transform-react-jsx-source")
|
320
|
+
);
|
296
321
|
}
|
297
322
|
if (!skipReactImport && !importReactRE.test(code)) {
|
298
|
-
|
323
|
+
prependReactImport = true;
|
299
324
|
}
|
300
325
|
}
|
301
326
|
}
|
327
|
+
let inputMap;
|
328
|
+
if (prependReactImport) {
|
329
|
+
if (needHiresSourcemap) {
|
330
|
+
const s = new MagicString__default(code);
|
331
|
+
s.prepend(prependReactImportCode);
|
332
|
+
code = s.toString();
|
333
|
+
inputMap = s.generateMap({ hires: true, source: id });
|
334
|
+
} else {
|
335
|
+
code = prependReactImportCode + code;
|
336
|
+
}
|
337
|
+
}
|
302
338
|
const shouldSkip = !plugins.length && !babelOptions.configFile && !(isProjectFile && babelOptions.babelrc);
|
303
339
|
if (shouldSkip) {
|
304
|
-
return
|
340
|
+
return {
|
341
|
+
code,
|
342
|
+
map: inputMap ?? null
|
343
|
+
};
|
305
344
|
}
|
306
345
|
const parserPlugins = [
|
307
346
|
...babelOptions.parserOpts.plugins,
|
@@ -337,7 +376,7 @@ function viteReact(opts = {}) {
|
|
337
376
|
},
|
338
377
|
plugins,
|
339
378
|
sourceMaps: true,
|
340
|
-
inputSourceMap: false
|
379
|
+
inputSourceMap: inputMap ?? false
|
341
380
|
});
|
342
381
|
if (result) {
|
343
382
|
let code2 = result.code;
|
package/dist/index.mjs
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
import path from 'node:path';
|
2
2
|
import * as babel from '@babel/core';
|
3
3
|
import { createFilter, normalizePath } from 'vite';
|
4
|
+
import MagicString from 'magic-string';
|
4
5
|
import fs from 'node:fs';
|
5
6
|
import { createRequire } from 'node:module';
|
6
7
|
|
7
8
|
const runtimePublicPath = "/@react-refresh";
|
8
9
|
const _require = createRequire(import.meta.url);
|
9
|
-
const reactRefreshDir = path.dirname(
|
10
|
-
|
10
|
+
const reactRefreshDir = path.dirname(
|
11
|
+
_require.resolve("react-refresh/package.json")
|
12
|
+
);
|
13
|
+
const runtimeFilePath = path.join(
|
14
|
+
reactRefreshDir,
|
15
|
+
"cjs/react-refresh-runtime.development.js"
|
16
|
+
);
|
11
17
|
const runtimeCode = `
|
12
18
|
const exports = {}
|
13
19
|
${fs.readFileSync(runtimeFilePath, "utf-8")}
|
@@ -72,8 +78,12 @@ function isRefreshBoundary(ast) {
|
|
72
78
|
}
|
73
79
|
const { declaration, specifiers } = node;
|
74
80
|
if (declaration) {
|
81
|
+
if (declaration.type === "ClassDeclaration")
|
82
|
+
return false;
|
75
83
|
if (declaration.type === "VariableDeclaration") {
|
76
|
-
return declaration.declarations.every(
|
84
|
+
return declaration.declarations.every(
|
85
|
+
(variable) => isComponentLikeIdentifier(variable.id)
|
86
|
+
);
|
77
87
|
}
|
78
88
|
if (declaration.type === "FunctionDeclaration") {
|
79
89
|
return !!declaration.id && isComponentLikeIdentifier(declaration.id);
|
@@ -97,9 +107,17 @@ function babelImportToRequire({ types: t }) {
|
|
97
107
|
ImportDeclaration(path) {
|
98
108
|
const decl = path.node;
|
99
109
|
const spec = decl.specifiers[0];
|
100
|
-
path.replaceWith(
|
101
|
-
t.
|
102
|
-
|
110
|
+
path.replaceWith(
|
111
|
+
t.variableDeclaration("var", [
|
112
|
+
t.variableDeclarator(
|
113
|
+
spec.local,
|
114
|
+
t.memberExpression(
|
115
|
+
t.callExpression(t.identifier("require"), [decl.source]),
|
116
|
+
spec.imported
|
117
|
+
)
|
118
|
+
)
|
119
|
+
])
|
120
|
+
);
|
103
121
|
}
|
104
122
|
}
|
105
123
|
};
|
@@ -122,20 +140,11 @@ async function restoreJSX(babel, code, filename) {
|
|
122
140
|
if (!reactAlias) {
|
123
141
|
return jsxNotFound;
|
124
142
|
}
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
return "React.Fragment";
|
131
|
-
}).replace(new RegExp(createElementPattern, "g"), (original, component) => {
|
132
|
-
if (/^[a-z][\w$]*$/.test(component)) {
|
133
|
-
return original;
|
134
|
-
}
|
135
|
-
hasCompiledJsx = true;
|
136
|
-
return "React.createElement(" + (component === "Fragment" ? "React.Fragment" : component);
|
137
|
-
});
|
138
|
-
if (!hasCompiledJsx) {
|
143
|
+
const reactJsxRE = new RegExp(
|
144
|
+
`\\b${reactAlias}\\.(createElement|Fragment)\\b`,
|
145
|
+
"g"
|
146
|
+
);
|
147
|
+
if (!reactJsxRE.test(code)) {
|
139
148
|
return jsxNotFound;
|
140
149
|
}
|
141
150
|
const result = await babel.transformAsync(code, {
|
@@ -147,12 +156,14 @@ async function restoreJSX(babel, code, filename) {
|
|
147
156
|
parserOpts: {
|
148
157
|
plugins: ["jsx"]
|
149
158
|
},
|
150
|
-
plugins: [await getBabelRestoreJSX()]
|
159
|
+
plugins: [[await getBabelRestoreJSX(), { reactAlias }]]
|
151
160
|
});
|
152
161
|
return [result?.ast, isCommonJS];
|
153
162
|
}
|
154
163
|
function parseReactAlias(code) {
|
155
|
-
let match = code.match(
|
164
|
+
let match = code.match(
|
165
|
+
/\b(var|let|const)\s+([^=\{\s]+)\s*=\s*require\(["']react["']\)/
|
166
|
+
);
|
156
167
|
if (match) {
|
157
168
|
return [match[2], true];
|
158
169
|
}
|
@@ -163,10 +174,12 @@ function parseReactAlias(code) {
|
|
163
174
|
return [void 0, false];
|
164
175
|
}
|
165
176
|
|
177
|
+
const prependReactImportCode = "import React from 'react'; ";
|
166
178
|
function viteReact(opts = {}) {
|
167
179
|
let devBase = "/";
|
168
180
|
let resolvedCacheDir;
|
169
181
|
let filter = createFilter(opts.include, opts.exclude);
|
182
|
+
let needHiresSourcemap = false;
|
170
183
|
let isProduction = true;
|
171
184
|
let projectRoot = process.cwd();
|
172
185
|
let skipFastRefresh = opts.fastRefresh === false;
|
@@ -197,25 +210,30 @@ function viteReact(opts = {}) {
|
|
197
210
|
filter = createFilter(opts.include, opts.exclude, {
|
198
211
|
resolve: projectRoot
|
199
212
|
});
|
213
|
+
needHiresSourcemap = config.command === "build" && !!config.build.sourcemap;
|
200
214
|
isProduction = config.isProduction;
|
201
215
|
skipFastRefresh || (skipFastRefresh = isProduction || config.command === "build");
|
202
216
|
const jsxInject = config.esbuild && config.esbuild.jsxInject;
|
203
217
|
if (jsxInject && importReactRE.test(jsxInject)) {
|
204
218
|
skipReactImport = true;
|
205
|
-
config.logger.warn(
|
219
|
+
config.logger.warn(
|
220
|
+
"[@vitejs/plugin-react] This plugin imports React for you automatically, so you can stop using `esbuild.jsxInject` for that purpose."
|
221
|
+
);
|
206
222
|
}
|
207
223
|
config.plugins.forEach((plugin) => {
|
208
224
|
const hasConflict = plugin.name === "react-refresh" || plugin !== viteReactJsx && plugin.name === "vite:react-jsx";
|
209
225
|
if (hasConflict)
|
210
|
-
return config.logger.warn(
|
226
|
+
return config.logger.warn(
|
227
|
+
`[@vitejs/plugin-react] You should stop using "${plugin.name}" since this plugin conflicts with it.`
|
228
|
+
);
|
211
229
|
});
|
212
230
|
runPluginOverrides = (babelOptions, context) => {
|
213
231
|
const hooks = config.plugins.map((plugin) => plugin.api?.reactBabel).filter(Boolean);
|
214
232
|
if (hooks.length > 0) {
|
215
|
-
return (runPluginOverrides = (babelOptions2) => {
|
216
|
-
hooks.forEach((hook) => hook(babelOptions2,
|
233
|
+
return (runPluginOverrides = (babelOptions2, context2) => {
|
234
|
+
hooks.forEach((hook) => hook(babelOptions2, context2, config));
|
217
235
|
return true;
|
218
|
-
})(babelOptions);
|
236
|
+
})(babelOptions, context);
|
219
237
|
}
|
220
238
|
runPluginOverrides = () => false;
|
221
239
|
return false;
|
@@ -253,13 +271,16 @@ function viteReact(opts = {}) {
|
|
253
271
|
}
|
254
272
|
}
|
255
273
|
let ast;
|
274
|
+
let prependReactImport = false;
|
256
275
|
if (!isProjectFile || isJSX) {
|
257
276
|
if (useAutomaticRuntime) {
|
258
277
|
const isOptimizedReactDom = id.startsWith(resolvedCacheDir) && id.includes("/react-dom.js");
|
259
278
|
const [restoredAst, isCommonJS] = !isProjectFile && !isJSX && !isOptimizedReactDom ? await restoreJSX(babel, code, id) : [null, false];
|
260
279
|
if (isJSX || (ast = restoredAst)) {
|
261
280
|
plugins.push([
|
262
|
-
await loadPlugin(
|
281
|
+
await loadPlugin(
|
282
|
+
"@babel/plugin-transform-react-jsx" + (isProduction ? "" : "-development")
|
283
|
+
),
|
263
284
|
{
|
264
285
|
runtime: "automatic",
|
265
286
|
importSource: opts.jsxImportSource,
|
@@ -272,16 +293,33 @@ function viteReact(opts = {}) {
|
|
272
293
|
}
|
273
294
|
} else if (isProjectFile) {
|
274
295
|
if (!isProduction) {
|
275
|
-
plugins.push(
|
296
|
+
plugins.push(
|
297
|
+
await loadPlugin("@babel/plugin-transform-react-jsx-self"),
|
298
|
+
await loadPlugin("@babel/plugin-transform-react-jsx-source")
|
299
|
+
);
|
276
300
|
}
|
277
301
|
if (!skipReactImport && !importReactRE.test(code)) {
|
278
|
-
|
302
|
+
prependReactImport = true;
|
279
303
|
}
|
280
304
|
}
|
281
305
|
}
|
306
|
+
let inputMap;
|
307
|
+
if (prependReactImport) {
|
308
|
+
if (needHiresSourcemap) {
|
309
|
+
const s = new MagicString(code);
|
310
|
+
s.prepend(prependReactImportCode);
|
311
|
+
code = s.toString();
|
312
|
+
inputMap = s.generateMap({ hires: true, source: id });
|
313
|
+
} else {
|
314
|
+
code = prependReactImportCode + code;
|
315
|
+
}
|
316
|
+
}
|
282
317
|
const shouldSkip = !plugins.length && !babelOptions.configFile && !(isProjectFile && babelOptions.babelrc);
|
283
318
|
if (shouldSkip) {
|
284
|
-
return
|
319
|
+
return {
|
320
|
+
code,
|
321
|
+
map: inputMap ?? null
|
322
|
+
};
|
285
323
|
}
|
286
324
|
const parserPlugins = [
|
287
325
|
...babelOptions.parserOpts.plugins,
|
@@ -317,7 +355,7 @@ function viteReact(opts = {}) {
|
|
317
355
|
},
|
318
356
|
plugins,
|
319
357
|
sourceMaps: true,
|
320
|
-
inputSourceMap: false
|
358
|
+
inputSourceMap: inputMap ?? false
|
321
359
|
});
|
322
360
|
if (result) {
|
323
361
|
let code2 = result.code;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vitejs/plugin-react",
|
3
|
-
"version": "2.0.
|
3
|
+
"version": "2.0.1",
|
4
4
|
"license": "MIT",
|
5
5
|
"author": "Evan You",
|
6
6
|
"contributors": [
|
@@ -23,11 +23,11 @@
|
|
23
23
|
"scripts": {
|
24
24
|
"dev": "unbuild --stub",
|
25
25
|
"build": "unbuild && pnpm run patch-cjs",
|
26
|
-
"patch-cjs": "
|
26
|
+
"patch-cjs": "tsx ../../scripts/patchCJS.ts",
|
27
27
|
"prepublishOnly": "npm run build"
|
28
28
|
},
|
29
29
|
"engines": {
|
30
|
-
"node": "
|
30
|
+
"node": "^14.18.0 || >=16.0.0"
|
31
31
|
},
|
32
32
|
"repository": {
|
33
33
|
"type": "git",
|
@@ -39,15 +39,16 @@
|
|
39
39
|
},
|
40
40
|
"homepage": "https://github.com/vitejs/vite/tree/main/packages/plugin-react#readme",
|
41
41
|
"dependencies": {
|
42
|
-
"@babel/core": "^7.18.
|
43
|
-
"@babel/plugin-transform-react-jsx": "^7.
|
44
|
-
"@babel/plugin-transform-react-jsx-development": "^7.
|
45
|
-
"@babel/plugin-transform-react-jsx-self": "^7.
|
46
|
-
"@babel/plugin-transform-react-jsx-source": "^7.
|
42
|
+
"@babel/core": "^7.18.10",
|
43
|
+
"@babel/plugin-transform-react-jsx": "^7.18.10",
|
44
|
+
"@babel/plugin-transform-react-jsx-development": "^7.18.6",
|
45
|
+
"@babel/plugin-transform-react-jsx-self": "^7.18.6",
|
46
|
+
"@babel/plugin-transform-react-jsx-source": "^7.18.6",
|
47
|
+
"magic-string": "^0.26.2",
|
47
48
|
"react-refresh": "^0.14.0"
|
48
49
|
},
|
49
50
|
"peerDependencies": {
|
50
|
-
"vite": "^3.0.0
|
51
|
+
"vite": "^3.0.0"
|
51
52
|
},
|
52
53
|
"devDependencies": {
|
53
54
|
"vite": "workspace:*"
|
package/src/fast-refresh.ts
CHANGED
@@ -92,6 +92,7 @@ export function isRefreshBoundary(ast: t.File): boolean {
|
|
92
92
|
}
|
93
93
|
const { declaration, specifiers } = node
|
94
94
|
if (declaration) {
|
95
|
+
if (declaration.type === 'ClassDeclaration') return false
|
95
96
|
if (declaration.type === 'VariableDeclaration') {
|
96
97
|
return declaration.declarations.every((variable) =>
|
97
98
|
isComponentLikeIdentifier(variable.id)
|
package/src/index.ts
CHANGED
@@ -3,6 +3,8 @@ import type { ParserOptions, TransformOptions, types as t } from '@babel/core'
|
|
3
3
|
import * as babel from '@babel/core'
|
4
4
|
import { createFilter, normalizePath } from 'vite'
|
5
5
|
import type { Plugin, PluginOption, ResolvedConfig } from 'vite'
|
6
|
+
import MagicString from 'magic-string'
|
7
|
+
import type { SourceMap } from 'magic-string'
|
6
8
|
import {
|
7
9
|
addRefreshWrapper,
|
8
10
|
isRefreshBoundary,
|
@@ -88,11 +90,14 @@ declare module 'vite' {
|
|
88
90
|
}
|
89
91
|
}
|
90
92
|
|
93
|
+
const prependReactImportCode = "import React from 'react'; "
|
94
|
+
|
91
95
|
export default function viteReact(opts: Options = {}): PluginOption[] {
|
92
96
|
// Provide default values for Rollup compat.
|
93
97
|
let devBase = '/'
|
94
98
|
let resolvedCacheDir: string
|
95
99
|
let filter = createFilter(opts.include, opts.exclude)
|
100
|
+
let needHiresSourcemap = false
|
96
101
|
let isProduction = true
|
97
102
|
let projectRoot = process.cwd()
|
98
103
|
let skipFastRefresh = opts.fastRefresh === false
|
@@ -135,6 +140,8 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
|
|
135
140
|
filter = createFilter(opts.include, opts.exclude, {
|
136
141
|
resolve: projectRoot
|
137
142
|
})
|
143
|
+
needHiresSourcemap =
|
144
|
+
config.command === 'build' && !!config.build.sourcemap
|
138
145
|
isProduction = config.isProduction
|
139
146
|
skipFastRefresh ||= isProduction || config.command === 'build'
|
140
147
|
|
@@ -165,10 +172,10 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
|
|
165
172
|
.filter(Boolean) as ReactBabelHook[]
|
166
173
|
|
167
174
|
if (hooks.length > 0) {
|
168
|
-
return (runPluginOverrides = (babelOptions) => {
|
175
|
+
return (runPluginOverrides = (babelOptions, context) => {
|
169
176
|
hooks.forEach((hook) => hook(babelOptions, context, config))
|
170
177
|
return true
|
171
|
-
})(babelOptions)
|
178
|
+
})(babelOptions, context)
|
172
179
|
}
|
173
180
|
runPluginOverrides = () => false
|
174
181
|
return false
|
@@ -217,6 +224,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
|
|
217
224
|
}
|
218
225
|
|
219
226
|
let ast: t.File | null | undefined
|
227
|
+
let prependReactImport = false
|
220
228
|
if (!isProjectFile || isJSX) {
|
221
229
|
if (useAutomaticRuntime) {
|
222
230
|
// By reverse-compiling "React.createElement" calls into JSX,
|
@@ -261,11 +269,23 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
|
|
261
269
|
// Even if the automatic JSX runtime is not used, we can still
|
262
270
|
// inject the React import for .jsx and .tsx modules.
|
263
271
|
if (!skipReactImport && !importReactRE.test(code)) {
|
264
|
-
|
272
|
+
prependReactImport = true
|
265
273
|
}
|
266
274
|
}
|
267
275
|
}
|
268
276
|
|
277
|
+
let inputMap: SourceMap | undefined
|
278
|
+
if (prependReactImport) {
|
279
|
+
if (needHiresSourcemap) {
|
280
|
+
const s = new MagicString(code)
|
281
|
+
s.prepend(prependReactImportCode)
|
282
|
+
code = s.toString()
|
283
|
+
inputMap = s.generateMap({ hires: true, source: id })
|
284
|
+
} else {
|
285
|
+
code = prependReactImportCode + code
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
269
289
|
// Plugins defined through this Vite plugin are only applied
|
270
290
|
// to modules within the project root, but "babel.config.js"
|
271
291
|
// files can define plugins that need to be applied to every
|
@@ -275,8 +295,12 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
|
|
275
295
|
!babelOptions.configFile &&
|
276
296
|
!(isProjectFile && babelOptions.babelrc)
|
277
297
|
|
298
|
+
// Avoid parsing if no plugins exist.
|
278
299
|
if (shouldSkip) {
|
279
|
-
return
|
300
|
+
return {
|
301
|
+
code,
|
302
|
+
map: inputMap ?? null
|
303
|
+
}
|
280
304
|
}
|
281
305
|
|
282
306
|
const parserPlugins: typeof babelOptions.parserOpts.plugins = [
|
@@ -323,7 +347,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
|
|
323
347
|
plugins,
|
324
348
|
sourceMaps: true,
|
325
349
|
// Vite handles sourcemap flattening
|
326
|
-
inputSourceMap: false as any
|
350
|
+
inputSourceMap: inputMap ?? (false as any)
|
327
351
|
})
|
328
352
|
|
329
353
|
if (result) {
|
@@ -4,6 +4,10 @@
|
|
4
4
|
*/
|
5
5
|
import type * as babel from '@babel/core'
|
6
6
|
|
7
|
+
interface PluginOptions {
|
8
|
+
reactAlias: string
|
9
|
+
}
|
10
|
+
|
7
11
|
/**
|
8
12
|
* Visitor factory for babel, converting React.createElement(...) to <jsx ...>...</jsx>
|
9
13
|
*
|
@@ -17,7 +21,10 @@ import type * as babel from '@babel/core'
|
|
17
21
|
*
|
18
22
|
* Any of those arguments might also be missing (undefined) and/or invalid.
|
19
23
|
*/
|
20
|
-
export default function (
|
24
|
+
export default function (
|
25
|
+
{ types: t }: typeof babel,
|
26
|
+
{ reactAlias = 'React' }: PluginOptions
|
27
|
+
): babel.PluginObj {
|
21
28
|
/**
|
22
29
|
* Get a `JSXElement` from a `CallExpression`.
|
23
30
|
* Returns `null` if this impossible.
|
@@ -48,7 +55,7 @@ export default function ({ types: t }: typeof babel): babel.PluginObj {
|
|
48
55
|
if (
|
49
56
|
t.isJSXMemberExpression(name) &&
|
50
57
|
t.isJSXIdentifier(name.object) &&
|
51
|
-
name.object.name ===
|
58
|
+
name.object.name === reactAlias &&
|
52
59
|
name.property.name === 'Fragment'
|
53
60
|
) {
|
54
61
|
return t.jsxFragment(
|
@@ -182,7 +189,7 @@ export default function ({ types: t }: typeof babel): babel.PluginObj {
|
|
182
189
|
const isReactCreateElement = (node: any) =>
|
183
190
|
t.isCallExpression(node) &&
|
184
191
|
t.isMemberExpression(node.callee) &&
|
185
|
-
t.isIdentifier(node.callee.object, { name:
|
192
|
+
t.isIdentifier(node.callee.object, { name: reactAlias }) &&
|
186
193
|
t.isIdentifier(node.callee.property, { name: 'createElement' }) &&
|
187
194
|
!node.callee.computed
|
188
195
|
|
@@ -81,7 +81,10 @@ describe('restore-jsx', () => {
|
|
81
81
|
expect(
|
82
82
|
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
83
83
|
React__default.createElement(foo)`)
|
84
|
-
).
|
84
|
+
).toMatchInlineSnapshot(`
|
85
|
+
"import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
86
|
+
React__default.createElement(foo);"
|
87
|
+
`)
|
85
88
|
expect(
|
86
89
|
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
87
90
|
React__default.createElement("h1")`)
|
@@ -104,7 +107,12 @@ describe('restore-jsx', () => {
|
|
104
107
|
expect(
|
105
108
|
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
106
109
|
React__default.createElement(foo, {hi: there})`)
|
107
|
-
).
|
110
|
+
).toMatchInlineSnapshot(`
|
111
|
+
"import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
112
|
+
React__default.createElement(foo, {
|
113
|
+
hi: there
|
114
|
+
});"
|
115
|
+
`)
|
108
116
|
expect(
|
109
117
|
await jsx(`import React__default, { PureComponent, Component, forwardRef, memo, createElement } from 'react';
|
110
118
|
React__default.createElement("h1", {hi: there})`)
|
@@ -114,4 +122,26 @@ describe('restore-jsx', () => {
|
|
114
122
|
React__default.createElement(Foo, {hi: there})`)
|
115
123
|
).toMatch(`<Foo hi={there} />;`)
|
116
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
|
+
})
|
117
147
|
})
|
@@ -33,34 +33,12 @@ export async function restoreJSX(
|
|
33
33
|
return jsxNotFound
|
34
34
|
}
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
+
)
|
62
40
|
|
63
|
-
if (!
|
41
|
+
if (!reactJsxRE.test(code)) {
|
64
42
|
return jsxNotFound
|
65
43
|
}
|
66
44
|
|
@@ -73,7 +51,7 @@ export async function restoreJSX(
|
|
73
51
|
parserOpts: {
|
74
52
|
plugins: ['jsx']
|
75
53
|
},
|
76
|
-
plugins: [await getBabelRestoreJSX()]
|
54
|
+
plugins: [[await getBabelRestoreJSX(), { reactAlias }]]
|
77
55
|
})
|
78
56
|
|
79
57
|
return [result?.ast, isCommonJS]
|