@vitejs/plugin-react 2.0.0-alpha.1 → 2.0.0-beta.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/index.cjs CHANGED
@@ -1,11 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('node:path');
3
4
  const babel = require('@babel/core');
4
- const pluginutils = require('@rollup/pluginutils');
5
- const resolve = require('resolve');
6
- const fs = require('fs');
7
- const path = require('path');
8
- const module$1 = require('module');
5
+ const vite = require('vite');
6
+ const fs = require('node:fs');
7
+ const node_module = require('node:module');
9
8
 
10
9
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
11
10
 
@@ -21,13 +20,12 @@ function _interopNamespace(e) {
21
20
  return n;
22
21
  }
23
22
 
23
+ const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
24
24
  const babel__namespace = /*#__PURE__*/_interopNamespace(babel);
25
- const resolve__default = /*#__PURE__*/_interopDefaultLegacy(resolve);
26
25
  const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
27
- const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
28
26
 
29
27
  const runtimePublicPath = "/@react-refresh";
30
- const _require = module$1.createRequire((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)));
28
+ 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
29
  const reactRefreshDir = path__default.dirname(_require.resolve("react-refresh/package.json"));
32
30
  const runtimeFilePath = path__default.join(reactRefreshDir, "cjs/react-refresh-runtime.development.js");
33
31
  const runtimeCode = `
@@ -140,9 +138,6 @@ async function getBabelRestoreJSX() {
140
138
  return babelRestoreJSX;
141
139
  }
142
140
  async function restoreJSX(babel, code, filename) {
143
- if (filename.includes("/.vite/react-dom.js")) {
144
- return jsxNotFound;
145
- }
146
141
  const [reactAlias, isCommonJS] = parseReactAlias(code);
147
142
  if (!reactAlias) {
148
143
  return jsxNotFound;
@@ -177,11 +172,11 @@ async function restoreJSX(babel, code, filename) {
177
172
  return [result?.ast, isCommonJS];
178
173
  }
179
174
  function parseReactAlias(code) {
180
- let match = code.match(/\b(var|let|const) +(\w+) *= *require\(["']react["']\)/);
175
+ let match = code.match(/\b(var|let|const)\s+([^=\{\s]+)\s*=\s*require\(["']react["']\)/);
181
176
  if (match) {
182
177
  return [match[2], true];
183
178
  }
184
- match = code.match(/^import (\w+).+? from ["']react["']/m);
179
+ match = code.match(/^import\s+(?:\*\s+as\s+)?(\w+).+?\bfrom\s*["']react["']/m);
185
180
  if (match) {
186
181
  return [match[1], false];
187
182
  }
@@ -189,33 +184,37 @@ function parseReactAlias(code) {
189
184
  }
190
185
 
191
186
  function viteReact(opts = {}) {
192
- var _a;
193
- let base = "/";
194
- let filter = pluginutils.createFilter(opts.include, opts.exclude);
187
+ let devBase = "/";
188
+ let resolvedCacheDir;
189
+ let filter = vite.createFilter(opts.include, opts.exclude);
195
190
  let isProduction = true;
196
191
  let projectRoot = process.cwd();
197
192
  let skipFastRefresh = opts.fastRefresh === false;
198
193
  let skipReactImport = false;
194
+ let runPluginOverrides = (options, context) => false;
195
+ let staticBabelOptions;
199
196
  const useAutomaticRuntime = opts.jsxRuntime !== "classic";
200
- const babelOptions = {
201
- babelrc: false,
202
- configFile: false,
203
- ...opts.babel
204
- };
205
- babelOptions.plugins || (babelOptions.plugins = []);
206
- babelOptions.presets || (babelOptions.presets = []);
207
- babelOptions.overrides || (babelOptions.overrides = []);
208
- babelOptions.parserOpts || (babelOptions.parserOpts = {});
209
- (_a = babelOptions.parserOpts).plugins || (_a.plugins = []);
210
197
  const importReactRE = /(^|\n)import\s+(\*\s+as\s+)?React(,|\s+)/;
211
198
  const fileExtensionRE = /\.[^\/\s\?]+$/;
212
199
  const viteBabel = {
213
200
  name: "vite:react-babel",
214
201
  enforce: "pre",
202
+ config() {
203
+ if (opts.jsxRuntime === "classic") {
204
+ return {
205
+ esbuild: {
206
+ logOverride: {
207
+ "this-is-undefined-in-esm": "silent"
208
+ }
209
+ }
210
+ };
211
+ }
212
+ },
215
213
  configResolved(config) {
216
- base = config.base;
214
+ devBase = config.base;
217
215
  projectRoot = config.root;
218
- filter = pluginutils.createFilter(opts.include, opts.exclude, {
216
+ resolvedCacheDir = vite.normalizePath(path__default.resolve(config.cacheDir));
217
+ filter = vite.createFilter(opts.include, opts.exclude, {
219
218
  resolve: projectRoot
220
219
  });
221
220
  isProduction = config.isProduction;
@@ -229,19 +228,38 @@ function viteReact(opts = {}) {
229
228
  const hasConflict = plugin.name === "react-refresh" || plugin !== viteReactJsx && plugin.name === "vite:react-jsx";
230
229
  if (hasConflict)
231
230
  return config.logger.warn(`[@vitejs/plugin-react] You should stop using "${plugin.name}" since this plugin conflicts with it.`);
232
- if (plugin.api?.reactBabel) {
233
- plugin.api.reactBabel(babelOptions, config);
234
- }
235
231
  });
232
+ runPluginOverrides = (babelOptions, context) => {
233
+ const hooks = config.plugins.map((plugin) => plugin.api?.reactBabel).filter(Boolean);
234
+ if (hooks.length > 0) {
235
+ return (runPluginOverrides = (babelOptions2) => {
236
+ hooks.forEach((hook) => hook(babelOptions2, context, config));
237
+ return true;
238
+ })(babelOptions);
239
+ }
240
+ runPluginOverrides = () => false;
241
+ return false;
242
+ };
236
243
  },
237
244
  async transform(code, id, options) {
238
- const ssr = typeof options === "boolean" ? options : options?.ssr === true;
245
+ const ssr = options?.ssr === true;
239
246
  const [filepath, querystring = ""] = id.split("?");
240
247
  const [extension = ""] = querystring.match(fileExtensionRE) || filepath.match(fileExtensionRE) || [];
241
248
  if (/\.(mjs|[tj]sx?)$/.test(extension)) {
242
249
  const isJSX = extension.endsWith("x");
243
250
  const isNodeModules = id.includes("/node_modules/");
244
251
  const isProjectFile = !isNodeModules && (id[0] === "\0" || id.startsWith(projectRoot + "/"));
252
+ let babelOptions = staticBabelOptions;
253
+ if (typeof opts.babel === "function") {
254
+ const rawOptions = opts.babel(id, { ssr });
255
+ babelOptions = createBabelOptions(rawOptions);
256
+ runPluginOverrides(babelOptions, { ssr, id });
257
+ } else if (!babelOptions) {
258
+ babelOptions = createBabelOptions(opts.babel);
259
+ if (!runPluginOverrides(babelOptions, { ssr, id })) {
260
+ staticBabelOptions = babelOptions;
261
+ }
262
+ }
245
263
  const plugins = isProjectFile ? [...babelOptions.plugins] : [];
246
264
  let useFastRefresh = false;
247
265
  if (!skipFastRefresh && !ssr && !isNodeModules) {
@@ -257,7 +275,8 @@ function viteReact(opts = {}) {
257
275
  let ast;
258
276
  if (!isProjectFile || isJSX) {
259
277
  if (useAutomaticRuntime) {
260
- const [restoredAst, isCommonJS] = !isProjectFile && !isJSX ? await restoreJSX(babel__namespace, code, id) : [null, false];
278
+ const isOptimizedReactDom = id.startsWith(resolvedCacheDir) && id.includes("/react-dom.js");
279
+ const [restoredAst, isCommonJS] = !isProjectFile && !isJSX && !isOptimizedReactDom ? await restoreJSX(babel__namespace, code, id) : [null, false];
261
280
  if (isJSX || (ast = restoredAst)) {
262
281
  plugins.push([
263
282
  await loadPlugin("@babel/plugin-transform-react-jsx" + (isProduction ? "" : "-development")),
@@ -358,34 +377,47 @@ function viteReact(opts = {}) {
358
377
  {
359
378
  tag: "script",
360
379
  attrs: { type: "module" },
361
- children: preambleCode.replace(`__BASE__`, base)
380
+ children: preambleCode.replace(`__BASE__`, devBase)
362
381
  }
363
382
  ];
364
383
  }
365
384
  };
366
- const runtimeId = "react/jsx-runtime";
385
+ const reactJsxRuntimeId = "react/jsx-runtime";
386
+ const reactJsxDevRuntimeId = "react/jsx-dev-runtime";
387
+ const virtualReactJsxRuntimeId = "\0" + reactJsxRuntimeId;
388
+ const virtualReactJsxDevRuntimeId = "\0" + reactJsxDevRuntimeId;
367
389
  const viteReactJsx = {
368
390
  name: "vite:react-jsx",
369
391
  enforce: "pre",
370
392
  config() {
371
393
  return {
372
394
  optimizeDeps: {
373
- include: ["react/jsx-dev-runtime"]
395
+ include: [reactJsxRuntimeId, reactJsxDevRuntimeId]
374
396
  }
375
397
  };
376
398
  },
377
- resolveId(id) {
378
- return id === runtimeId ? id : null;
399
+ resolveId(id, importer) {
400
+ if (id === reactJsxRuntimeId && importer !== virtualReactJsxRuntimeId) {
401
+ return virtualReactJsxRuntimeId;
402
+ }
403
+ if (id === reactJsxDevRuntimeId && importer !== virtualReactJsxDevRuntimeId) {
404
+ return virtualReactJsxDevRuntimeId;
405
+ }
379
406
  },
380
407
  load(id) {
381
- if (id === runtimeId) {
382
- const runtimePath = resolve__default.sync(runtimeId, {
383
- basedir: projectRoot
384
- });
385
- const exports = ["jsx", "jsxs", "Fragment"];
408
+ if (id === virtualReactJsxRuntimeId) {
386
409
  return [
387
- `import * as jsxRuntime from ${JSON.stringify(runtimePath)}`,
388
- ...exports.map((name) => `export const ${name} = jsxRuntime.${name}`)
410
+ `import * as jsxRuntime from ${JSON.stringify(reactJsxRuntimeId)}`,
411
+ `export const Fragment = jsxRuntime.Fragment`,
412
+ `export const jsx = jsxRuntime.jsx`,
413
+ `export const jsxs = jsxRuntime.jsxs`
414
+ ].join("\n");
415
+ }
416
+ if (id === virtualReactJsxDevRuntimeId) {
417
+ return [
418
+ `import * as jsxRuntime from ${JSON.stringify(reactJsxDevRuntimeId)}`,
419
+ `export const Fragment = jsxRuntime.Fragment`,
420
+ `export const jsxDEV = jsxRuntime.jsxDEV`
389
421
  ].join("\n");
390
422
  }
391
423
  }
@@ -393,8 +425,22 @@ function viteReact(opts = {}) {
393
425
  return [viteBabel, viteReactRefresh, useAutomaticRuntime && viteReactJsx];
394
426
  }
395
427
  viteReact.preambleCode = preambleCode;
396
- function loadPlugin(path) {
397
- return import(path).then((module) => module.default || module);
428
+ function loadPlugin(path2) {
429
+ return import(path2).then((module) => module.default || module);
430
+ }
431
+ function createBabelOptions(rawOptions) {
432
+ var _a;
433
+ const babelOptions = {
434
+ babelrc: false,
435
+ configFile: false,
436
+ ...rawOptions
437
+ };
438
+ babelOptions.plugins || (babelOptions.plugins = []);
439
+ babelOptions.presets || (babelOptions.presets = []);
440
+ babelOptions.overrides || (babelOptions.overrides = []);
441
+ babelOptions.parserOpts || (babelOptions.parserOpts = {});
442
+ (_a = babelOptions.parserOpts).plugins || (_a.plugins = []);
443
+ return babelOptions;
398
444
  }
399
445
 
400
446
  module.exports = viteReact;
package/dist/index.d.ts CHANGED
@@ -29,7 +29,9 @@ interface Options {
29
29
  /**
30
30
  * Babel configuration applied in both dev and prod.
31
31
  */
32
- babel?: BabelOptions;
32
+ babel?: BabelOptions | ((id: string, options: {
33
+ ssr?: boolean;
34
+ }) => BabelOptions);
33
35
  }
34
36
  declare type BabelOptions = Omit<TransformOptions, 'ast' | 'filename' | 'root' | 'sourceFileName' | 'sourceMaps' | 'inputSourceMap'>;
35
37
  /**
@@ -44,13 +46,18 @@ interface ReactBabelOptions extends BabelOptions {
44
46
  plugins: Extract<ParserOptions['plugins'], any[]>;
45
47
  };
46
48
  }
49
+ declare type ReactBabelHook = (babelConfig: ReactBabelOptions, context: ReactBabelHookContext, config: ResolvedConfig) => void;
50
+ declare type ReactBabelHookContext = {
51
+ ssr: boolean;
52
+ id: string;
53
+ };
47
54
  declare module 'vite' {
48
55
  interface Plugin {
49
56
  api?: {
50
57
  /**
51
58
  * Manipulate the Babel options of `@vitejs/plugin-react`
52
59
  */
53
- reactBabel?: (options: ReactBabelOptions, config: ResolvedConfig) => void;
60
+ reactBabel?: ReactBabelHook;
54
61
  };
55
62
  }
56
63
  }
package/dist/index.mjs CHANGED
@@ -1,9 +1,8 @@
1
+ import path from 'node:path';
1
2
  import * as babel from '@babel/core';
2
- import { createFilter } from '@rollup/pluginutils';
3
- import resolve from 'resolve';
4
- import fs from 'fs';
5
- import path from 'path';
6
- import { createRequire } from 'module';
3
+ import { createFilter, normalizePath } from 'vite';
4
+ import fs from 'node:fs';
5
+ import { createRequire } from 'node:module';
7
6
 
8
7
  const runtimePublicPath = "/@react-refresh";
9
8
  const _require = createRequire(import.meta.url);
@@ -119,9 +118,6 @@ async function getBabelRestoreJSX() {
119
118
  return babelRestoreJSX;
120
119
  }
121
120
  async function restoreJSX(babel, code, filename) {
122
- if (filename.includes("/.vite/react-dom.js")) {
123
- return jsxNotFound;
124
- }
125
121
  const [reactAlias, isCommonJS] = parseReactAlias(code);
126
122
  if (!reactAlias) {
127
123
  return jsxNotFound;
@@ -156,11 +152,11 @@ async function restoreJSX(babel, code, filename) {
156
152
  return [result?.ast, isCommonJS];
157
153
  }
158
154
  function parseReactAlias(code) {
159
- let match = code.match(/\b(var|let|const) +(\w+) *= *require\(["']react["']\)/);
155
+ let match = code.match(/\b(var|let|const)\s+([^=\{\s]+)\s*=\s*require\(["']react["']\)/);
160
156
  if (match) {
161
157
  return [match[2], true];
162
158
  }
163
- match = code.match(/^import (\w+).+? from ["']react["']/m);
159
+ match = code.match(/^import\s+(?:\*\s+as\s+)?(\w+).+?\bfrom\s*["']react["']/m);
164
160
  if (match) {
165
161
  return [match[1], false];
166
162
  }
@@ -168,32 +164,36 @@ function parseReactAlias(code) {
168
164
  }
169
165
 
170
166
  function viteReact(opts = {}) {
171
- var _a;
172
- let base = "/";
167
+ let devBase = "/";
168
+ let resolvedCacheDir;
173
169
  let filter = createFilter(opts.include, opts.exclude);
174
170
  let isProduction = true;
175
171
  let projectRoot = process.cwd();
176
172
  let skipFastRefresh = opts.fastRefresh === false;
177
173
  let skipReactImport = false;
174
+ let runPluginOverrides = (options, context) => false;
175
+ let staticBabelOptions;
178
176
  const useAutomaticRuntime = opts.jsxRuntime !== "classic";
179
- const babelOptions = {
180
- babelrc: false,
181
- configFile: false,
182
- ...opts.babel
183
- };
184
- babelOptions.plugins || (babelOptions.plugins = []);
185
- babelOptions.presets || (babelOptions.presets = []);
186
- babelOptions.overrides || (babelOptions.overrides = []);
187
- babelOptions.parserOpts || (babelOptions.parserOpts = {});
188
- (_a = babelOptions.parserOpts).plugins || (_a.plugins = []);
189
177
  const importReactRE = /(^|\n)import\s+(\*\s+as\s+)?React(,|\s+)/;
190
178
  const fileExtensionRE = /\.[^\/\s\?]+$/;
191
179
  const viteBabel = {
192
180
  name: "vite:react-babel",
193
181
  enforce: "pre",
182
+ config() {
183
+ if (opts.jsxRuntime === "classic") {
184
+ return {
185
+ esbuild: {
186
+ logOverride: {
187
+ "this-is-undefined-in-esm": "silent"
188
+ }
189
+ }
190
+ };
191
+ }
192
+ },
194
193
  configResolved(config) {
195
- base = config.base;
194
+ devBase = config.base;
196
195
  projectRoot = config.root;
196
+ resolvedCacheDir = normalizePath(path.resolve(config.cacheDir));
197
197
  filter = createFilter(opts.include, opts.exclude, {
198
198
  resolve: projectRoot
199
199
  });
@@ -208,19 +208,38 @@ function viteReact(opts = {}) {
208
208
  const hasConflict = plugin.name === "react-refresh" || plugin !== viteReactJsx && plugin.name === "vite:react-jsx";
209
209
  if (hasConflict)
210
210
  return config.logger.warn(`[@vitejs/plugin-react] You should stop using "${plugin.name}" since this plugin conflicts with it.`);
211
- if (plugin.api?.reactBabel) {
212
- plugin.api.reactBabel(babelOptions, config);
213
- }
214
211
  });
212
+ runPluginOverrides = (babelOptions, context) => {
213
+ const hooks = config.plugins.map((plugin) => plugin.api?.reactBabel).filter(Boolean);
214
+ if (hooks.length > 0) {
215
+ return (runPluginOverrides = (babelOptions2) => {
216
+ hooks.forEach((hook) => hook(babelOptions2, context, config));
217
+ return true;
218
+ })(babelOptions);
219
+ }
220
+ runPluginOverrides = () => false;
221
+ return false;
222
+ };
215
223
  },
216
224
  async transform(code, id, options) {
217
- const ssr = typeof options === "boolean" ? options : options?.ssr === true;
225
+ const ssr = options?.ssr === true;
218
226
  const [filepath, querystring = ""] = id.split("?");
219
227
  const [extension = ""] = querystring.match(fileExtensionRE) || filepath.match(fileExtensionRE) || [];
220
228
  if (/\.(mjs|[tj]sx?)$/.test(extension)) {
221
229
  const isJSX = extension.endsWith("x");
222
230
  const isNodeModules = id.includes("/node_modules/");
223
231
  const isProjectFile = !isNodeModules && (id[0] === "\0" || id.startsWith(projectRoot + "/"));
232
+ let babelOptions = staticBabelOptions;
233
+ if (typeof opts.babel === "function") {
234
+ const rawOptions = opts.babel(id, { ssr });
235
+ babelOptions = createBabelOptions(rawOptions);
236
+ runPluginOverrides(babelOptions, { ssr, id });
237
+ } else if (!babelOptions) {
238
+ babelOptions = createBabelOptions(opts.babel);
239
+ if (!runPluginOverrides(babelOptions, { ssr, id })) {
240
+ staticBabelOptions = babelOptions;
241
+ }
242
+ }
224
243
  const plugins = isProjectFile ? [...babelOptions.plugins] : [];
225
244
  let useFastRefresh = false;
226
245
  if (!skipFastRefresh && !ssr && !isNodeModules) {
@@ -236,7 +255,8 @@ function viteReact(opts = {}) {
236
255
  let ast;
237
256
  if (!isProjectFile || isJSX) {
238
257
  if (useAutomaticRuntime) {
239
- const [restoredAst, isCommonJS] = !isProjectFile && !isJSX ? await restoreJSX(babel, code, id) : [null, false];
258
+ const isOptimizedReactDom = id.startsWith(resolvedCacheDir) && id.includes("/react-dom.js");
259
+ const [restoredAst, isCommonJS] = !isProjectFile && !isJSX && !isOptimizedReactDom ? await restoreJSX(babel, code, id) : [null, false];
240
260
  if (isJSX || (ast = restoredAst)) {
241
261
  plugins.push([
242
262
  await loadPlugin("@babel/plugin-transform-react-jsx" + (isProduction ? "" : "-development")),
@@ -337,34 +357,47 @@ function viteReact(opts = {}) {
337
357
  {
338
358
  tag: "script",
339
359
  attrs: { type: "module" },
340
- children: preambleCode.replace(`__BASE__`, base)
360
+ children: preambleCode.replace(`__BASE__`, devBase)
341
361
  }
342
362
  ];
343
363
  }
344
364
  };
345
- const runtimeId = "react/jsx-runtime";
365
+ const reactJsxRuntimeId = "react/jsx-runtime";
366
+ const reactJsxDevRuntimeId = "react/jsx-dev-runtime";
367
+ const virtualReactJsxRuntimeId = "\0" + reactJsxRuntimeId;
368
+ const virtualReactJsxDevRuntimeId = "\0" + reactJsxDevRuntimeId;
346
369
  const viteReactJsx = {
347
370
  name: "vite:react-jsx",
348
371
  enforce: "pre",
349
372
  config() {
350
373
  return {
351
374
  optimizeDeps: {
352
- include: ["react/jsx-dev-runtime"]
375
+ include: [reactJsxRuntimeId, reactJsxDevRuntimeId]
353
376
  }
354
377
  };
355
378
  },
356
- resolveId(id) {
357
- return id === runtimeId ? id : null;
379
+ resolveId(id, importer) {
380
+ if (id === reactJsxRuntimeId && importer !== virtualReactJsxRuntimeId) {
381
+ return virtualReactJsxRuntimeId;
382
+ }
383
+ if (id === reactJsxDevRuntimeId && importer !== virtualReactJsxDevRuntimeId) {
384
+ return virtualReactJsxDevRuntimeId;
385
+ }
358
386
  },
359
387
  load(id) {
360
- if (id === runtimeId) {
361
- const runtimePath = resolve.sync(runtimeId, {
362
- basedir: projectRoot
363
- });
364
- const exports = ["jsx", "jsxs", "Fragment"];
388
+ if (id === virtualReactJsxRuntimeId) {
365
389
  return [
366
- `import * as jsxRuntime from ${JSON.stringify(runtimePath)}`,
367
- ...exports.map((name) => `export const ${name} = jsxRuntime.${name}`)
390
+ `import * as jsxRuntime from ${JSON.stringify(reactJsxRuntimeId)}`,
391
+ `export const Fragment = jsxRuntime.Fragment`,
392
+ `export const jsx = jsxRuntime.jsx`,
393
+ `export const jsxs = jsxRuntime.jsxs`
394
+ ].join("\n");
395
+ }
396
+ if (id === virtualReactJsxDevRuntimeId) {
397
+ return [
398
+ `import * as jsxRuntime from ${JSON.stringify(reactJsxDevRuntimeId)}`,
399
+ `export const Fragment = jsxRuntime.Fragment`,
400
+ `export const jsxDEV = jsxRuntime.jsxDEV`
368
401
  ].join("\n");
369
402
  }
370
403
  }
@@ -372,8 +405,22 @@ function viteReact(opts = {}) {
372
405
  return [viteBabel, viteReactRefresh, useAutomaticRuntime && viteReactJsx];
373
406
  }
374
407
  viteReact.preambleCode = preambleCode;
375
- function loadPlugin(path) {
376
- return import(path).then((module) => module.default || module);
408
+ function loadPlugin(path2) {
409
+ return import(path2).then((module) => module.default || module);
410
+ }
411
+ function createBabelOptions(rawOptions) {
412
+ var _a;
413
+ const babelOptions = {
414
+ babelrc: false,
415
+ configFile: false,
416
+ ...rawOptions
417
+ };
418
+ babelOptions.plugins || (babelOptions.plugins = []);
419
+ babelOptions.presets || (babelOptions.presets = []);
420
+ babelOptions.overrides || (babelOptions.overrides = []);
421
+ babelOptions.parserOpts || (babelOptions.parserOpts = {});
422
+ (_a = babelOptions.parserOpts).plugins || (_a.plugins = []);
423
+ return babelOptions;
377
424
  }
378
425
 
379
426
  export { viteReact as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitejs/plugin-react",
3
- "version": "2.0.0-alpha.1",
3
+ "version": "2.0.0-beta.0",
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": "ts-node ../../scripts/patchCJS.ts",
26
+ "patch-cjs": "esno ../../scripts/patchCJS.ts",
27
27
  "prepublishOnly": "npm run build"
28
28
  },
29
29
  "engines": {
30
- "node": ">=14.6.0"
30
+ "node": ">=14.18.0"
31
31
  },
32
32
  "repository": {
33
33
  "type": "git",
@@ -39,17 +39,15 @@
39
39
  },
40
40
  "homepage": "https://github.com/vitejs/vite/tree/main/packages/plugin-react#readme",
41
41
  "dependencies": {
42
- "@babel/core": "^7.17.10",
43
- "@babel/plugin-transform-react-jsx": "^7.17.3",
42
+ "@babel/core": "^7.18.5",
43
+ "@babel/plugin-transform-react-jsx": "^7.17.12",
44
44
  "@babel/plugin-transform-react-jsx-development": "^7.16.7",
45
- "@babel/plugin-transform-react-jsx-self": "^7.16.7",
45
+ "@babel/plugin-transform-react-jsx-self": "^7.17.12",
46
46
  "@babel/plugin-transform-react-jsx-source": "^7.16.7",
47
- "@rollup/pluginutils": "^4.2.1",
48
- "react-refresh": "^0.13.0",
49
- "resolve": "^1.22.0"
47
+ "react-refresh": "^0.14.0"
50
48
  },
51
49
  "peerDependencies": {
52
- "vite": "^3.0.0-alpha"
50
+ "vite": "^3.0.0-alpha.11"
53
51
  },
54
52
  "devDependencies": {
55
53
  "vite": "workspace:*"
@@ -1,6 +1,6 @@
1
- import fs from 'fs'
2
- import path from 'path'
3
- import { createRequire } from 'module'
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { createRequire } from 'node:module'
4
4
  import type { types as t } from '@babel/core'
5
5
 
6
6
  export const runtimePublicPath = '/@react-refresh'
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
+ import path from 'node:path'
1
2
  import type { ParserOptions, TransformOptions, types as t } from '@babel/core'
2
3
  import * as babel from '@babel/core'
3
- import { createFilter } from '@rollup/pluginutils'
4
- import resolve from 'resolve'
4
+ import { createFilter, normalizePath } from 'vite'
5
5
  import type { Plugin, PluginOption, ResolvedConfig } from 'vite'
6
6
  import {
7
7
  addRefreshWrapper,
@@ -41,7 +41,9 @@ export interface Options {
41
41
  /**
42
42
  * Babel configuration applied in both dev and prod.
43
43
  */
44
- babel?: BabelOptions
44
+ babel?:
45
+ | BabelOptions
46
+ | ((id: string, options: { ssr?: boolean }) => BabelOptions)
45
47
  }
46
48
 
47
49
  export type BabelOptions = Omit<
@@ -67,40 +69,42 @@ export interface ReactBabelOptions extends BabelOptions {
67
69
  }
68
70
  }
69
71
 
72
+ type ReactBabelHook = (
73
+ babelConfig: ReactBabelOptions,
74
+ context: ReactBabelHookContext,
75
+ config: ResolvedConfig
76
+ ) => void
77
+
78
+ type ReactBabelHookContext = { ssr: boolean; id: string }
79
+
70
80
  declare module 'vite' {
71
81
  export interface Plugin {
72
82
  api?: {
73
83
  /**
74
84
  * Manipulate the Babel options of `@vitejs/plugin-react`
75
85
  */
76
- reactBabel?: (options: ReactBabelOptions, config: ResolvedConfig) => void
86
+ reactBabel?: ReactBabelHook
77
87
  }
78
88
  }
79
89
  }
80
90
 
81
91
  export default function viteReact(opts: Options = {}): PluginOption[] {
82
92
  // Provide default values for Rollup compat.
83
- let base = '/'
93
+ let devBase = '/'
94
+ let resolvedCacheDir: string
84
95
  let filter = createFilter(opts.include, opts.exclude)
85
96
  let isProduction = true
86
97
  let projectRoot = process.cwd()
87
98
  let skipFastRefresh = opts.fastRefresh === false
88
99
  let skipReactImport = false
100
+ let runPluginOverrides = (
101
+ options: ReactBabelOptions,
102
+ context: ReactBabelHookContext
103
+ ) => false
104
+ let staticBabelOptions: ReactBabelOptions | undefined
89
105
 
90
106
  const useAutomaticRuntime = opts.jsxRuntime !== 'classic'
91
107
 
92
- const babelOptions = {
93
- babelrc: false,
94
- configFile: false,
95
- ...opts.babel
96
- } as ReactBabelOptions
97
-
98
- babelOptions.plugins ||= []
99
- babelOptions.presets ||= []
100
- babelOptions.overrides ||= []
101
- babelOptions.parserOpts ||= {} as any
102
- babelOptions.parserOpts.plugins ||= []
103
-
104
108
  // Support patterns like:
105
109
  // - import * as React from 'react';
106
110
  // - import React from 'react';
@@ -113,9 +117,21 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
113
117
  const viteBabel: Plugin = {
114
118
  name: 'vite:react-babel',
115
119
  enforce: 'pre',
120
+ config() {
121
+ if (opts.jsxRuntime === 'classic') {
122
+ return {
123
+ esbuild: {
124
+ logOverride: {
125
+ 'this-is-undefined-in-esm': 'silent'
126
+ }
127
+ }
128
+ }
129
+ }
130
+ },
116
131
  configResolved(config) {
117
- base = config.base
132
+ devBase = config.base
118
133
  projectRoot = config.root
134
+ resolvedCacheDir = normalizePath(path.resolve(config.cacheDir))
119
135
  filter = createFilter(opts.include, opts.exclude, {
120
136
  resolve: projectRoot
121
137
  })
@@ -141,14 +157,25 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
141
157
  `[@vitejs/plugin-react] You should stop using "${plugin.name}" ` +
142
158
  `since this plugin conflicts with it.`
143
159
  )
160
+ })
161
+
162
+ runPluginOverrides = (babelOptions, context) => {
163
+ const hooks = config.plugins
164
+ .map((plugin) => plugin.api?.reactBabel)
165
+ .filter(Boolean) as ReactBabelHook[]
144
166
 
145
- if (plugin.api?.reactBabel) {
146
- plugin.api.reactBabel(babelOptions, config)
167
+ if (hooks.length > 0) {
168
+ return (runPluginOverrides = (babelOptions) => {
169
+ hooks.forEach((hook) => hook(babelOptions, context, config))
170
+ return true
171
+ })(babelOptions)
147
172
  }
148
- })
173
+ runPluginOverrides = () => false
174
+ return false
175
+ }
149
176
  },
150
177
  async transform(code, id, options) {
151
- const ssr = typeof options === 'boolean' ? options : options?.ssr === true
178
+ const ssr = options?.ssr === true
152
179
  // File extension could be mocked/overridden in querystring.
153
180
  const [filepath, querystring = ''] = id.split('?')
154
181
  const [extension = ''] =
@@ -162,6 +189,18 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
162
189
  const isProjectFile =
163
190
  !isNodeModules && (id[0] === '\0' || id.startsWith(projectRoot + '/'))
164
191
 
192
+ let babelOptions = staticBabelOptions
193
+ if (typeof opts.babel === 'function') {
194
+ const rawOptions = opts.babel(id, { ssr })
195
+ babelOptions = createBabelOptions(rawOptions)
196
+ runPluginOverrides(babelOptions, { ssr, id: id })
197
+ } else if (!babelOptions) {
198
+ babelOptions = createBabelOptions(opts.babel)
199
+ if (!runPluginOverrides(babelOptions, { ssr, id: id })) {
200
+ staticBabelOptions = babelOptions
201
+ }
202
+ }
203
+
165
204
  const plugins = isProjectFile ? [...babelOptions.plugins] : []
166
205
 
167
206
  let useFastRefresh = false
@@ -183,8 +222,12 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
183
222
  // By reverse-compiling "React.createElement" calls into JSX,
184
223
  // React elements provided by dependencies will also use the
185
224
  // automatic runtime!
225
+ // Avoid parsing the optimized react-dom since it will never
226
+ // contain compiled JSX and it's a pretty big file (800kb).
227
+ const isOptimizedReactDom =
228
+ id.startsWith(resolvedCacheDir) && id.includes('/react-dom.js')
186
229
  const [restoredAst, isCommonJS] =
187
- !isProjectFile && !isJSX
230
+ !isProjectFile && !isJSX && !isOptimizedReactDom
188
231
  ? await restoreJSX(babel, code, id)
189
232
  : [null, false]
190
233
 
@@ -322,13 +365,16 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
322
365
  {
323
366
  tag: 'script',
324
367
  attrs: { type: 'module' },
325
- children: preambleCode.replace(`__BASE__`, base)
368
+ children: preambleCode.replace(`__BASE__`, devBase)
326
369
  }
327
370
  ]
328
371
  }
329
372
  }
330
373
 
331
- const runtimeId = 'react/jsx-runtime'
374
+ const reactJsxRuntimeId = 'react/jsx-runtime'
375
+ const reactJsxDevRuntimeId = 'react/jsx-dev-runtime'
376
+ const virtualReactJsxRuntimeId = '\0' + reactJsxRuntimeId
377
+ const virtualReactJsxDevRuntimeId = '\0' + reactJsxDevRuntimeId
332
378
  // Adapted from https://github.com/alloc/vite-react-jsx
333
379
  const viteReactJsx: Plugin = {
334
380
  name: 'vite:react-jsx',
@@ -336,25 +382,39 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
336
382
  config() {
337
383
  return {
338
384
  optimizeDeps: {
339
- include: ['react/jsx-dev-runtime']
385
+ include: [reactJsxRuntimeId, reactJsxDevRuntimeId]
340
386
  }
341
387
  }
342
388
  },
343
- resolveId(id: string) {
344
- return id === runtimeId ? id : null
389
+ resolveId(id, importer) {
390
+ // Resolve runtime to a virtual path to be interoped.
391
+ // Since the interop code re-imports `id`, we need to prevent re-resolving
392
+ // to the virtual id if the importer is already the virtual id.
393
+ if (id === reactJsxRuntimeId && importer !== virtualReactJsxRuntimeId) {
394
+ return virtualReactJsxRuntimeId
395
+ }
396
+ if (
397
+ id === reactJsxDevRuntimeId &&
398
+ importer !== virtualReactJsxDevRuntimeId
399
+ ) {
400
+ return virtualReactJsxDevRuntimeId
401
+ }
345
402
  },
346
- load(id: string) {
347
- if (id === runtimeId) {
348
- const runtimePath = resolve.sync(runtimeId, {
349
- basedir: projectRoot
350
- })
351
- const exports = ['jsx', 'jsxs', 'Fragment']
403
+ load(id) {
404
+ // Apply manual interop
405
+ if (id === virtualReactJsxRuntimeId) {
406
+ return [
407
+ `import * as jsxRuntime from ${JSON.stringify(reactJsxRuntimeId)}`,
408
+ `export const Fragment = jsxRuntime.Fragment`,
409
+ `export const jsx = jsxRuntime.jsx`,
410
+ `export const jsxs = jsxRuntime.jsxs`
411
+ ].join('\n')
412
+ }
413
+ if (id === virtualReactJsxDevRuntimeId) {
352
414
  return [
353
- `import * as jsxRuntime from ${JSON.stringify(runtimePath)}`,
354
- // We can't use `export * from` or else any callsite that uses
355
- // this module will be compiled to `jsxRuntime.exports.jsx`
356
- // instead of the more concise `jsx` alias.
357
- ...exports.map((name) => `export const ${name} = jsxRuntime.${name}`)
415
+ `import * as jsxRuntime from ${JSON.stringify(reactJsxDevRuntimeId)}`,
416
+ `export const Fragment = jsxRuntime.Fragment`,
417
+ `export const jsxDEV = jsxRuntime.jsxDEV`
358
418
  ].join('\n')
359
419
  }
360
420
  }
@@ -368,3 +428,19 @@ viteReact.preambleCode = preambleCode
368
428
  function loadPlugin(path: string): Promise<any> {
369
429
  return import(path).then((module) => module.default || module)
370
430
  }
431
+
432
+ function createBabelOptions(rawOptions?: BabelOptions) {
433
+ const babelOptions = {
434
+ babelrc: false,
435
+ configFile: false,
436
+ ...rawOptions
437
+ } as ReactBabelOptions
438
+
439
+ babelOptions.plugins ||= []
440
+ babelOptions.presets ||= []
441
+ babelOptions.overrides ||= []
442
+ babelOptions.parserOpts ||= {} as any
443
+ babelOptions.parserOpts.plugins ||= []
444
+
445
+ return babelOptions
446
+ }
@@ -1,6 +1,67 @@
1
1
  import * as babel from '@babel/core'
2
2
  import { describe, expect, it } from 'vitest'
3
- import { restoreJSX } from './restore-jsx'
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
+ })
4
65
 
5
66
  async function jsx(sourceCode: string) {
6
67
  const [ast] = await restoreJSX(babel, sourceCode, 'test.js')
@@ -27,12 +27,6 @@ export async function restoreJSX(
27
27
  code: string,
28
28
  filename: string
29
29
  ): Promise<RestoredJSX> {
30
- // Avoid parsing the optimized react-dom since it will never
31
- // contain compiled JSX and it's a pretty big file (800kb).
32
- if (filename.includes('/.vite/react-dom.js')) {
33
- return jsxNotFound
34
- }
35
-
36
30
  const [reactAlias, isCommonJS] = parseReactAlias(code)
37
31
 
38
32
  if (!reactAlias) {
@@ -85,16 +79,16 @@ export async function restoreJSX(
85
79
  return [result?.ast, isCommonJS]
86
80
  }
87
81
 
88
- function parseReactAlias(
82
+ export function parseReactAlias(
89
83
  code: string
90
84
  ): [alias: string | undefined, isCommonJS: boolean] {
91
85
  let match = code.match(
92
- /\b(var|let|const) +(\w+) *= *require\(["']react["']\)/
86
+ /\b(var|let|const)\s+([^=\{\s]+)\s*=\s*require\(["']react["']\)/
93
87
  )
94
88
  if (match) {
95
89
  return [match[2], true]
96
90
  }
97
- match = code.match(/^import (\w+).+? from ["']react["']/m)
91
+ match = code.match(/^import\s+(?:\*\s+as\s+)?(\w+).+?\bfrom\s*["']react["']/m)
98
92
  if (match) {
99
93
  return [match[1], false]
100
94
  }