@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 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/#server-middlewaremode), you should make sure your entry `index.html` file is transformed by Vite. Here's an example for an Express server:
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 === "React" && name.property.name === "Fragment") {
26
- return t.jsxFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), children);
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((prop) => t.isObjectProperty(prop) ? t.jsxAttribute(getJSXIdentifier(prop.key), getJSXAttributeValue(prop.value)) : t.jsxSpreadAttribute(prop.argument));
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: "React" }) && t.isIdentifier(node.callee.property, { name: "createElement" }) && !node.callee.computed;
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((property) => t.isSpreadElement(property) || t.isObjectProperty(property, { computed: false }) && getJSXIdentifier(property.key) != null && getJSXAttributeValue(property.value) != null);
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 === "React" && name.property.name === "Fragment") {
24
- return t.jsxFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), children);
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((prop) => t.isObjectProperty(prop) ? t.jsxAttribute(getJSXIdentifier(prop.key), getJSXAttributeValue(prop.value)) : t.jsxSpreadAttribute(prop.argument));
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: "React" }) && t.isIdentifier(node.callee.property, { name: "createElement" }) && !node.callee.computed;
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((property) => t.isSpreadElement(property) || t.isObjectProperty(property, { computed: false }) && getJSXIdentifier(property.key) != null && getJSXAttributeValue(property.value) != null);
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(_require.resolve("react-refresh/package.json"));
30
- const runtimeFilePath = path__default.join(reactRefreshDir, "cjs/react-refresh-runtime.development.js");
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((variable) => isComponentLikeIdentifier(variable.id));
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(t.variableDeclaration("var", [
121
- t.variableDeclarator(spec.local, t.memberExpression(t.callExpression(t.identifier("require"), [decl.source]), spec.imported))
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
- let hasCompiledJsx = false;
146
- const fragmentPattern = `\\b${reactAlias}\\.Fragment\\b`;
147
- const createElementPattern = `\\b${reactAlias}\\.createElement\\(\\s*([A-Z"'][\\w$.]*["']?)`;
148
- code = code.replace(new RegExp(fragmentPattern, "g"), () => {
149
- hasCompiledJsx = true;
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(/\b(var|let|const)\s+([^=\{\s]+)\s*=\s*require\(["']react["']\)/);
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("[@vitejs/plugin-react] This plugin imports React for you automatically, so you can stop using `esbuild.jsxInject` for that purpose.");
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(`[@vitejs/plugin-react] You should stop using "${plugin.name}" since this plugin conflicts with it.`);
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, context, config));
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("@babel/plugin-transform-react-jsx" + (isProduction ? "" : "-development")),
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(await loadPlugin("@babel/plugin-transform-react-jsx-self"), await loadPlugin("@babel/plugin-transform-react-jsx-source"));
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
- code = `import React from 'react'; ` + code;
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(_require.resolve("react-refresh/package.json"));
10
- const runtimeFilePath = path.join(reactRefreshDir, "cjs/react-refresh-runtime.development.js");
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((variable) => isComponentLikeIdentifier(variable.id));
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(t.variableDeclaration("var", [
101
- t.variableDeclarator(spec.local, t.memberExpression(t.callExpression(t.identifier("require"), [decl.source]), spec.imported))
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
- let hasCompiledJsx = false;
126
- const fragmentPattern = `\\b${reactAlias}\\.Fragment\\b`;
127
- const createElementPattern = `\\b${reactAlias}\\.createElement\\(\\s*([A-Z"'][\\w$.]*["']?)`;
128
- code = code.replace(new RegExp(fragmentPattern, "g"), () => {
129
- hasCompiledJsx = true;
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(/\b(var|let|const)\s+([^=\{\s]+)\s*=\s*require\(["']react["']\)/);
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("[@vitejs/plugin-react] This plugin imports React for you automatically, so you can stop using `esbuild.jsxInject` for that purpose.");
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(`[@vitejs/plugin-react] You should stop using "${plugin.name}" since this plugin conflicts with it.`);
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, context, config));
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("@babel/plugin-transform-react-jsx" + (isProduction ? "" : "-development")),
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(await loadPlugin("@babel/plugin-transform-react-jsx-self"), await loadPlugin("@babel/plugin-transform-react-jsx-source"));
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
- code = `import React from 'react'; ` + code;
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.0-beta.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": "esno ../../scripts/patchCJS.ts",
26
+ "patch-cjs": "tsx ../../scripts/patchCJS.ts",
27
27
  "prepublishOnly": "npm run build"
28
28
  },
29
29
  "engines": {
30
- "node": ">=14.18.0"
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.5",
43
- "@babel/plugin-transform-react-jsx": "^7.17.12",
44
- "@babel/plugin-transform-react-jsx-development": "^7.16.7",
45
- "@babel/plugin-transform-react-jsx-self": "^7.17.12",
46
- "@babel/plugin-transform-react-jsx-source": "^7.16.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-alpha.11"
51
+ "vite": "^3.0.0"
51
52
  },
52
53
  "devDependencies": {
53
54
  "vite": "workspace:*"
@@ -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
- code = `import React from 'react'; ` + code
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 // Avoid parsing if no plugins exist.
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 ({ types: t }: typeof babel): babel.PluginObj {
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 === 'React' &&
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: 'React' }) &&
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
- ).toBeNull()
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
- ).toBeNull()
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
- let hasCompiledJsx = false
37
-
38
- const fragmentPattern = `\\b${reactAlias}\\.Fragment\\b`
39
- const createElementPattern = `\\b${reactAlias}\\.createElement\\(\\s*([A-Z"'][\\w$.]*["']?)`
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 (!hasCompiledJsx) {
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]