@vitejs/plugin-react 2.0.0-alpha.0 → 2.0.0-alpha.3

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