@vitejs/plugin-react 3.1.0 → 4.0.0-beta.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
@@ -1,12 +1,11 @@
1
1
  # @vitejs/plugin-react [![npm](https://img.shields.io/npm/v/@vitejs/plugin-react.svg)](https://npmjs.com/package/@vitejs/plugin-react)
2
2
 
3
- The all-in-one Vite plugin for React projects.
3
+ The default Vite plugin for React projects.
4
4
 
5
5
  - enable [Fast Refresh](https://www.npmjs.com/package/react-refresh) in development
6
- - use the [automatic JSX runtime](https://github.com/alloc/vite-react-jsx#faq)
7
- - avoid manual `import React` in `.jsx` and `.tsx` modules
8
- - dedupe the `react` and `react-dom` packages
6
+ - use the [automatic JSX runtime](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html)
9
7
  - use custom Babel plugins/presets
8
+ - small installation size
10
9
 
11
10
  ```js
12
11
  // vite.config.js
@@ -18,44 +17,38 @@ export default defineConfig({
18
17
  })
19
18
  ```
20
19
 
21
- ## Filter which files use Fast Refresh
20
+ ## Options
22
21
 
23
- By default, Fast Refresh is used by files ending with `.js`, `.jsx`, `.ts`, and `.tsx`, except for files with a `node_modules` parent directory.
22
+ ### include/exclude
24
23
 
25
- In some situations, you may not want a file to act as a HMR boundary, instead preferring that the changes propagate higher in the stack before being handled. In these cases, you can provide an `include` and/or `exclude` option, which can be a regex, a [picomatch](https://github.com/micromatch/picomatch#globbing-features) pattern, or an array of either. Files matching `include` and not `exclude` will use Fast Refresh. The defaults are always applied.
24
+ Includes `.js`, `.jsx`, `.ts` & `.tsx` by default. This option can be used to add fast refresh to `.mdx` files:
26
25
 
27
26
  ```js
28
- react({
29
- // Exclude storybook stories
30
- exclude: /\.stories\.(t|j)sx?$/,
31
- // Only .tsx files
32
- include: '**/*.tsx',
33
- })
34
- ```
35
-
36
- ### Configure the JSX import source
37
-
38
- Control where the JSX factory is imported from. This option is ignored for classic `jsxRuntime`.
27
+ import { defineConfig } from 'vite'
28
+ import react from '@vitejs/plugin-react'
29
+ import mdx from '@mdx-js/rollup'
39
30
 
40
- ```js
41
- react({
42
- jsxImportSource: '@emotion/react',
31
+ export default defineConfig({
32
+ plugins: [
33
+ { enforce: 'pre', ...mdx() },
34
+ react({ include: /\.(mdx|js|jsx|ts|tsx)$/ }),
35
+ ],
43
36
  })
44
37
  ```
45
38
 
46
- ## Opting out of the automatic JSX runtime
39
+ > `node_modules` are never processed by this plugin (but esbuild will)
40
+
41
+ ### jsxImportSource
47
42
 
48
- By default, the plugin uses the [automatic JSX runtime](https://github.com/alloc/vite-react-jsx#faq). However, if you encounter any issues, you may opt out using the `jsxRuntime` option.
43
+ Control where the JSX factory is imported from. Default to `'react'`
49
44
 
50
45
  ```js
51
- react({
52
- jsxRuntime: 'classic',
53
- })
46
+ react({ jsxImportSource: '@emotion/react' })
54
47
  ```
55
48
 
56
- ## Babel configuration
49
+ ### babel
57
50
 
58
- The `babel` option lets you add plugins, presets, and [other configuration](https://babeljs.io/docs/en/options) to the Babel transformation performed on each JSX/TSX file.
51
+ The `babel` option lets you add plugins, presets, and [other configuration](https://babeljs.io/docs/en/options) to the Babel transformation performed on each included file.
59
52
 
60
53
  ```js
61
54
  react({
@@ -71,7 +64,9 @@ react({
71
64
  })
72
65
  ```
73
66
 
74
- ### Proposed syntax
67
+ Note: When not using plugins, only esbuild is used for production builds, resulting in faster builds.
68
+
69
+ #### Proposed syntax
75
70
 
76
71
  If you are using ES syntax that are still in proposal status (e.g. class properties), you can selectively enable them with the `babel.parserOpts.plugins` option:
77
72
 
package/dist/index.cjs CHANGED
@@ -1,13 +1,16 @@
1
1
  'use strict';
2
2
 
3
- const path = require('node:path');
4
3
  const babel = require('@babel/core');
5
4
  const vite = require('vite');
6
5
  const MagicString = require('magic-string');
7
6
  const fs = require('node:fs');
7
+ const path = require('node:path');
8
8
  const node_module = require('node:module');
9
9
 
10
- function _interopNamespaceDefault(e) {
10
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
11
+
12
+ function _interopNamespaceCompat(e) {
13
+ if (e && typeof e === 'object' && 'default' in e) return e;
11
14
  const n = Object.create(null);
12
15
  if (e) {
13
16
  for (const k in e) {
@@ -18,21 +21,24 @@ function _interopNamespaceDefault(e) {
18
21
  return n;
19
22
  }
20
23
 
21
- const babel__namespace = /*#__PURE__*/_interopNamespaceDefault(babel);
24
+ const babel__namespace = /*#__PURE__*/_interopNamespaceCompat(babel);
25
+ const MagicString__default = /*#__PURE__*/_interopDefaultCompat(MagicString);
26
+ const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
27
+ const path__default = /*#__PURE__*/_interopDefaultCompat(path);
22
28
 
23
29
  const runtimePublicPath = "/@react-refresh";
24
- const _require = node_module.createRequire((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)));
25
- const reactRefreshDir = path.dirname(
30
+ const _require = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)));
31
+ const reactRefreshDir = path__default.dirname(
26
32
  _require.resolve("react-refresh/package.json")
27
33
  );
28
- const runtimeFilePath = path.join(
34
+ const runtimeFilePath = path__default.join(
29
35
  reactRefreshDir,
30
36
  "cjs/react-refresh-runtime.development.js"
31
37
  );
32
38
  const runtimeCode = `
33
39
  const exports = {}
34
- ${fs.readFileSync(runtimeFilePath, "utf-8")}
35
- ${fs.readFileSync(_require.resolve("./refreshUtils.js"), "utf-8")}
40
+ ${fs__default.readFileSync(runtimeFilePath, "utf-8")}
41
+ ${fs__default.readFileSync(_require.resolve("./refreshUtils.js"), "utf-8")}
36
42
  export default exports
37
43
  `;
38
44
  const preambleCode = `
@@ -68,7 +74,7 @@ if (import.meta.hot) {
68
74
  window.$RefreshReg$ = prevRefreshReg;
69
75
  window.$RefreshSig$ = prevRefreshSig;
70
76
 
71
- import(/* @vite-ignore */ import.meta.url).then((currentExports) => {
77
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
72
78
  RefreshRuntime.registerExportsForReactRefresh(__SOURCE__, currentExports);
73
79
  import.meta.hot.accept((nextExports) => {
74
80
  if (!nextExports) return;
@@ -83,47 +89,34 @@ function addRefreshWrapper(code, id) {
83
89
 
84
90
  const prependReactImportCode = "import React from 'react'; ";
85
91
  const refreshContentRE = /\$Refresh(?:Reg|Sig)\$\(/;
92
+ const defaultIncludeRE = /\.[tj]sx?$/;
93
+ const tsRE = /\.tsx?$/;
86
94
  function viteReact(opts = {}) {
87
95
  let devBase = "/";
88
- let filter = vite.createFilter(opts.include, opts.exclude);
96
+ const filter = vite.createFilter(opts.include ?? defaultIncludeRE, opts.exclude);
97
+ const devRuntime = `${opts.jsxImportSource ?? "react"}/jsx-dev-runtime`;
89
98
  let needHiresSourcemap = false;
90
99
  let isProduction = true;
91
100
  let projectRoot = process.cwd();
92
- let skipFastRefresh = opts.fastRefresh === false;
93
- let skipReactImport = false;
94
- let runPluginOverrides = (options, context) => false;
101
+ let skipFastRefresh = false;
102
+ let runPluginOverrides;
95
103
  let staticBabelOptions;
96
- const useAutomaticRuntime = opts.jsxRuntime !== "classic";
97
104
  const importReactRE = /(?:^|\n)import\s+(?:\*\s+as\s+)?React(?:,|\s+)/;
98
- const fileExtensionRE = /\.[^/\s?]+$/;
99
105
  const viteBabel = {
100
106
  name: "vite:react-babel",
101
107
  enforce: "pre",
102
- config(userConfig, { mode }) {
103
- const resolvedRoot = vite.normalizePath(
104
- userConfig.root ? path.resolve(userConfig.root) : process.cwd()
105
- );
106
- const envDir = userConfig.envDir ? vite.normalizePath(path.resolve(resolvedRoot, userConfig.envDir)) : resolvedRoot;
107
- vite.loadEnv(mode, envDir, vite.resolveEnvPrefix(userConfig));
108
- const isProduction2 = (process.env.NODE_ENV || process.env.VITE_USER_NODE_ENV || mode) === "production";
108
+ config() {
109
109
  if (opts.jsxRuntime === "classic") {
110
110
  return {
111
111
  esbuild: {
112
- logOverride: {
113
- "this-is-undefined-in-esm": "silent"
114
- },
115
- jsx: "transform",
116
- jsxImportSource: opts.jsxImportSource,
117
- jsxSideEffects: opts.jsxPure === false
112
+ jsx: "transform"
118
113
  }
119
114
  };
120
115
  } else {
121
116
  return {
122
117
  esbuild: {
123
- jsxDev: !isProduction2,
124
118
  jsx: "automatic",
125
- jsxImportSource: opts.jsxImportSource,
126
- jsxSideEffects: opts.jsxPure === false
119
+ jsxImportSource: opts.jsxImportSource
127
120
  }
128
121
  };
129
122
  }
@@ -131,155 +124,126 @@ function viteReact(opts = {}) {
131
124
  configResolved(config) {
132
125
  devBase = config.base;
133
126
  projectRoot = config.root;
134
- filter = vite.createFilter(opts.include, opts.exclude, {
135
- resolve: projectRoot
136
- });
137
127
  needHiresSourcemap = config.command === "build" && !!config.build.sourcemap;
138
128
  isProduction = config.isProduction;
139
- skipFastRefresh || (skipFastRefresh = isProduction || config.command === "build");
140
- const jsxInject = config.esbuild && config.esbuild.jsxInject;
141
- if (jsxInject && importReactRE.test(jsxInject)) {
142
- skipReactImport = true;
143
- config.logger.warn(
144
- "[@vitejs/plugin-react] This plugin imports React for you automatically, so you can stop using `esbuild.jsxInject` for that purpose."
129
+ skipFastRefresh = isProduction || config.command === "build";
130
+ if (opts.jsxRuntime === "classic") {
131
+ config.logger.warnOnce(
132
+ "[@vitejs/plugin-react] Support for classic runtime is deprecated."
145
133
  );
146
134
  }
147
- config.plugins.forEach((plugin) => {
148
- const hasConflict = plugin.name === "react-refresh" || plugin !== viteReactJsx && plugin.name === "vite:react-jsx";
149
- if (hasConflict)
150
- return config.logger.warn(
151
- `[@vitejs/plugin-react] You should stop using "${plugin.name}" since this plugin conflicts with it.`
152
- );
153
- });
154
- runPluginOverrides = (babelOptions, context) => {
155
- const hooks = config.plugins.map((plugin) => plugin.api?.reactBabel).filter(Boolean);
156
- if (hooks.length > 0) {
157
- return (runPluginOverrides = (babelOptions2, context2) => {
158
- hooks.forEach((hook) => hook(babelOptions2, context2, config));
159
- return true;
160
- })(babelOptions, context);
161
- }
162
- runPluginOverrides = () => false;
163
- return false;
164
- };
135
+ if ("jsxPure" in opts) {
136
+ config.logger.warnOnce(
137
+ "[@vitejs/plugin-react] jsxPure was removed. You can configure esbuild.jsxSideEffects directly."
138
+ );
139
+ }
140
+ const hooks = config.plugins.map((plugin) => plugin.api?.reactBabel).filter(defined);
141
+ if (hooks.length > 0) {
142
+ runPluginOverrides = (babelOptions, context) => {
143
+ hooks.forEach((hook) => hook(babelOptions, context, config));
144
+ };
145
+ } else if (typeof opts.babel !== "function") {
146
+ staticBabelOptions = createBabelOptions(opts.babel);
147
+ }
165
148
  },
166
149
  async transform(code, id, options) {
150
+ if (id.includes("/node_modules/"))
151
+ return;
152
+ const [filepath] = id.split("?");
153
+ if (!filter(filepath))
154
+ return;
167
155
  const ssr = options?.ssr === true;
168
- const [filepath, querystring = ""] = id.split("?");
169
- const [extension = ""] = querystring.match(fileExtensionRE) || filepath.match(fileExtensionRE) || [];
170
- if (/\.(?:mjs|[tj]sx?)$/.test(extension)) {
171
- const isJSX = extension.endsWith("x");
172
- const isNodeModules = id.includes("/node_modules/");
173
- const isProjectFile = !isNodeModules && (id[0] === "\0" || id.startsWith(projectRoot + "/"));
174
- let babelOptions = staticBabelOptions;
175
- if (typeof opts.babel === "function") {
176
- const rawOptions = opts.babel(id, { ssr });
177
- babelOptions = createBabelOptions(rawOptions);
178
- runPluginOverrides(babelOptions, { ssr, id });
179
- } else if (!babelOptions) {
180
- babelOptions = createBabelOptions(opts.babel);
181
- if (!runPluginOverrides(babelOptions, { ssr, id })) {
182
- staticBabelOptions = babelOptions;
183
- }
184
- }
185
- const plugins = isProjectFile ? [...babelOptions.plugins] : [];
186
- let useFastRefresh = false;
187
- if (!skipFastRefresh && !ssr && !isNodeModules) {
188
- const isReactModule = isJSX || importReactRE.test(code);
189
- if (isReactModule && filter(id)) {
190
- useFastRefresh = true;
191
- plugins.push([
192
- await loadPlugin("react-refresh/babel"),
193
- { skipEnvCheck: true }
194
- ]);
195
- }
196
- }
197
- let prependReactImport = false;
198
- if (!isProjectFile || isJSX) {
199
- if (!useAutomaticRuntime && isProjectFile) {
200
- if (!isProduction) {
201
- plugins.push(
202
- await loadPlugin("@babel/plugin-transform-react-jsx-self"),
203
- await loadPlugin("@babel/plugin-transform-react-jsx-source")
204
- );
205
- }
206
- if (!skipReactImport && !importReactRE.test(code)) {
207
- prependReactImport = true;
208
- }
209
- }
210
- }
211
- let inputMap;
212
- if (prependReactImport) {
213
- if (needHiresSourcemap) {
214
- const s = new MagicString(code);
215
- s.prepend(prependReactImportCode);
216
- code = s.toString();
217
- inputMap = s.generateMap({ hires: true, source: id });
218
- } else {
219
- code = prependReactImportCode + code;
220
- }
221
- }
222
- const shouldSkip = !plugins.length && !babelOptions.configFile && !(isProjectFile && babelOptions.babelrc);
223
- if (shouldSkip) {
224
- return {
225
- code,
226
- map: inputMap ?? null
227
- };
156
+ const babelOptions = (() => {
157
+ if (staticBabelOptions)
158
+ return staticBabelOptions;
159
+ const newBabelOptions = createBabelOptions(
160
+ typeof opts.babel === "function" ? opts.babel(id, { ssr }) : opts.babel
161
+ );
162
+ runPluginOverrides?.(newBabelOptions, { id, ssr });
163
+ return newBabelOptions;
164
+ })();
165
+ const plugins = [...babelOptions.plugins];
166
+ const isJSX = filepath.endsWith("x");
167
+ const useFastRefresh = !skipFastRefresh && !ssr && (isJSX || (opts.jsxRuntime === "classic" ? code.includes(devRuntime) : importReactRE.test(code)));
168
+ if (useFastRefresh) {
169
+ plugins.push([
170
+ await loadPlugin("react-refresh/babel"),
171
+ { skipEnvCheck: true }
172
+ ]);
173
+ }
174
+ let prependReactImport = false;
175
+ if (opts.jsxRuntime === "classic" && isJSX) {
176
+ if (!isProduction) {
177
+ plugins.push(
178
+ await loadPlugin("@babel/plugin-transform-react-jsx-self"),
179
+ await loadPlugin("@babel/plugin-transform-react-jsx-source")
180
+ );
228
181
  }
229
- const parserPlugins = [
230
- ...babelOptions.parserOpts.plugins,
231
- "importMeta",
232
- // This plugin is applied before esbuild transforms the code,
233
- // so we need to enable some stage 3 syntax that is supported in
234
- // TypeScript and some environments already.
235
- "topLevelAwait",
236
- "classProperties",
237
- "classPrivateProperties",
238
- "classPrivateMethods"
239
- ];
240
- if (!extension.endsWith(".ts")) {
241
- parserPlugins.push("jsx");
182
+ if (!importReactRE.test(code)) {
183
+ prependReactImport = true;
242
184
  }
243
- if (/\.tsx?$/.test(extension)) {
244
- parserPlugins.push("typescript");
185
+ }
186
+ let inputMap;
187
+ if (prependReactImport) {
188
+ if (needHiresSourcemap) {
189
+ const s = new MagicString__default(code);
190
+ s.prepend(prependReactImportCode);
191
+ code = s.toString();
192
+ inputMap = s.generateMap({ hires: true, source: id });
193
+ } else {
194
+ code = prependReactImportCode + code;
245
195
  }
246
- const result = await babel__namespace.transformAsync(code, {
247
- ...babelOptions,
248
- root: projectRoot,
249
- filename: id,
250
- sourceFileName: filepath,
251
- parserOpts: {
252
- ...babelOptions.parserOpts,
253
- sourceType: "module",
254
- allowAwaitOutsideFunction: true,
255
- plugins: parserPlugins
256
- },
257
- generatorOpts: {
258
- ...babelOptions.generatorOpts,
259
- decoratorsBeforeExport: true
260
- },
261
- plugins,
262
- sourceMaps: true,
263
- // Vite handles sourcemap flattening
264
- inputSourceMap: inputMap ?? false
265
- });
266
- if (result) {
267
- let code2 = result.code;
268
- if (useFastRefresh && refreshContentRE.test(code2)) {
269
- code2 = addRefreshWrapper(code2, id);
270
- }
271
- return {
272
- code: code2,
273
- map: result.map
274
- };
196
+ }
197
+ if (!plugins.length && !babelOptions.configFile && !babelOptions.babelrc) {
198
+ return { code, map: inputMap ?? null };
199
+ }
200
+ const parserPlugins = [...babelOptions.parserOpts.plugins];
201
+ if (!filepath.endsWith(".ts")) {
202
+ parserPlugins.push("jsx");
203
+ }
204
+ if (tsRE.test(filepath)) {
205
+ parserPlugins.push("typescript");
206
+ }
207
+ const result = await babel__namespace.transformAsync(code, {
208
+ ...babelOptions,
209
+ root: projectRoot,
210
+ filename: id,
211
+ sourceFileName: filepath,
212
+ parserOpts: {
213
+ ...babelOptions.parserOpts,
214
+ sourceType: "module",
215
+ allowAwaitOutsideFunction: true,
216
+ plugins: parserPlugins
217
+ },
218
+ generatorOpts: {
219
+ ...babelOptions.generatorOpts,
220
+ decoratorsBeforeExport: true
221
+ },
222
+ plugins,
223
+ sourceMaps: true,
224
+ // Vite handles sourcemap flattening
225
+ inputSourceMap: inputMap ?? false
226
+ });
227
+ if (result) {
228
+ let code2 = result.code;
229
+ if (useFastRefresh && refreshContentRE.test(code2)) {
230
+ code2 = addRefreshWrapper(code2, id);
275
231
  }
232
+ return { code: code2, map: result.map };
276
233
  }
277
234
  }
278
235
  };
279
236
  const viteReactRefresh = {
280
237
  name: "vite:react-refresh",
281
238
  enforce: "pre",
282
- config: () => ({
239
+ config: (userConfig) => ({
240
+ build: silenceUseClientWarning(userConfig),
241
+ optimizeDeps: {
242
+ // We can't add `react-dom` because the dependency is `react-dom/client`
243
+ // for React 18 while it's `react-dom` for React 17. We'd need to detect
244
+ // what React version the user has installed.
245
+ include: ["react", devRuntime]
246
+ },
283
247
  resolve: {
284
248
  dedupe: ["react", "react-dom"]
285
249
  }
@@ -305,54 +269,35 @@ function viteReact(opts = {}) {
305
269
  ];
306
270
  }
307
271
  };
308
- const reactJsxRuntimeId = "react/jsx-runtime";
309
- const reactJsxDevRuntimeId = "react/jsx-dev-runtime";
310
- const virtualReactJsxRuntimeId = "\0" + reactJsxRuntimeId;
311
- const virtualReactJsxDevRuntimeId = "\0" + reactJsxDevRuntimeId;
312
- const viteReactJsx = {
313
- name: "vite:react-jsx",
314
- enforce: "pre",
315
- config() {
316
- return {
317
- optimizeDeps: {
318
- // We can't add `react-dom` because the dependency is `react-dom/client`
319
- // for React 18 while it's `react-dom` for React 17. We'd need to detect
320
- // what React version the user has installed.
321
- include: [reactJsxRuntimeId, reactJsxDevRuntimeId, "react"]
322
- }
323
- };
324
- },
325
- resolveId(id, importer) {
326
- if (id === reactJsxRuntimeId && importer !== virtualReactJsxRuntimeId) {
327
- return virtualReactJsxRuntimeId;
328
- }
329
- if (id === reactJsxDevRuntimeId && importer !== virtualReactJsxDevRuntimeId) {
330
- return virtualReactJsxDevRuntimeId;
331
- }
332
- },
333
- load(id) {
334
- if (id === virtualReactJsxRuntimeId) {
335
- return [
336
- `import * as jsxRuntime from ${JSON.stringify(reactJsxRuntimeId)}`,
337
- `export const Fragment = jsxRuntime.Fragment`,
338
- `export const jsx = jsxRuntime.jsx`,
339
- `export const jsxs = jsxRuntime.jsxs`
340
- ].join("\n");
272
+ return [viteBabel, viteReactRefresh];
273
+ }
274
+ viteReact.preambleCode = preambleCode;
275
+ const silenceUseClientWarning = (userConfig) => ({
276
+ rollupOptions: {
277
+ onwarn(warning, defaultHandler) {
278
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes("use client")) {
279
+ return;
341
280
  }
342
- if (id === virtualReactJsxDevRuntimeId) {
343
- return [
344
- `import * as jsxRuntime from ${JSON.stringify(reactJsxDevRuntimeId)}`,
345
- `export const Fragment = jsxRuntime.Fragment`,
346
- `export const jsxDEV = jsxRuntime.jsxDEV`
347
- ].join("\n");
281
+ if (userConfig.build?.rollupOptions?.onwarn) {
282
+ userConfig.build.rollupOptions.onwarn(warning, defaultHandler);
283
+ } else {
284
+ defaultHandler(warning);
348
285
  }
349
286
  }
350
- };
351
- return [viteBabel, viteReactRefresh, useAutomaticRuntime && viteReactJsx];
352
- }
353
- viteReact.preambleCode = preambleCode;
354
- function loadPlugin(path2) {
355
- return import(path2).then((module) => module.default || module);
287
+ }
288
+ });
289
+ const loadedPlugin = /* @__PURE__ */ new Map();
290
+ function loadPlugin(path) {
291
+ const cached = loadedPlugin.get(path);
292
+ if (cached)
293
+ return cached;
294
+ const promise = import(path).then((module) => {
295
+ const value = module.default || module;
296
+ loadedPlugin.set(path, value);
297
+ return value;
298
+ });
299
+ loadedPlugin.set(path, promise);
300
+ return promise;
356
301
  }
357
302
  function createBabelOptions(rawOptions) {
358
303
  var _a;
@@ -368,6 +313,9 @@ function createBabelOptions(rawOptions) {
368
313
  (_a = babelOptions.parserOpts).plugins || (_a.plugins = []);
369
314
  return babelOptions;
370
315
  }
316
+ function defined(value) {
317
+ return value !== void 0;
318
+ }
371
319
 
372
320
  module.exports = viteReact;
373
321
  module.exports.default = viteReact;
package/dist/index.d.ts CHANGED
@@ -5,27 +5,17 @@ interface Options {
5
5
  include?: string | RegExp | Array<string | RegExp>;
6
6
  exclude?: string | RegExp | Array<string | RegExp>;
7
7
  /**
8
- * Enable `react-refresh` integration. Vite disables this in prod env or build mode.
9
- * @default true
10
- */
11
- fastRefresh?: boolean;
12
- /**
13
- * Set this to `"automatic"` to use [vite-react-jsx](https://github.com/alloc/vite-react-jsx).
8
+ * @deprecated All tools now support the automatic runtime, and it has been backported
9
+ * up to React 16. This allows to skip the React import and can produce smaller bundlers.
14
10
  * @default "automatic"
15
11
  */
16
12
  jsxRuntime?: 'classic' | 'automatic';
17
13
  /**
18
14
  * Control where the JSX factory is imported from.
19
- * This option is ignored when `jsxRuntime` is not `"automatic"`.
20
- * @default "react"
15
+ * https://esbuild.github.io/api/#jsx-import-source
16
+ * @default 'react'
21
17
  */
22
18
  jsxImportSource?: string;
23
- /**
24
- * Set this to `true` to annotate the JSX factory with `\/* @__PURE__ *\/`.
25
- * This option is ignored when `jsxRuntime` is not `"automatic"`.
26
- * @default true
27
- */
28
- jsxPure?: boolean;
29
19
  /**
30
20
  * Babel configuration applied in both dev and prod.
31
21
  */
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import path from 'node:path';
2
1
  import * as babel from '@babel/core';
3
- import { createFilter, normalizePath, loadEnv, resolveEnvPrefix } from 'vite';
2
+ import { createFilter } from 'vite';
4
3
  import MagicString from 'magic-string';
5
4
  import fs from 'node:fs';
5
+ import path from 'node:path';
6
6
  import { createRequire } from 'node:module';
7
7
 
8
8
  const runtimePublicPath = "/@react-refresh";
@@ -53,7 +53,7 @@ if (import.meta.hot) {
53
53
  window.$RefreshReg$ = prevRefreshReg;
54
54
  window.$RefreshSig$ = prevRefreshSig;
55
55
 
56
- import(/* @vite-ignore */ import.meta.url).then((currentExports) => {
56
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
57
57
  RefreshRuntime.registerExportsForReactRefresh(__SOURCE__, currentExports);
58
58
  import.meta.hot.accept((nextExports) => {
59
59
  if (!nextExports) return;
@@ -68,47 +68,34 @@ function addRefreshWrapper(code, id) {
68
68
 
69
69
  const prependReactImportCode = "import React from 'react'; ";
70
70
  const refreshContentRE = /\$Refresh(?:Reg|Sig)\$\(/;
71
+ const defaultIncludeRE = /\.[tj]sx?$/;
72
+ const tsRE = /\.tsx?$/;
71
73
  function viteReact(opts = {}) {
72
74
  let devBase = "/";
73
- let filter = createFilter(opts.include, opts.exclude);
75
+ const filter = createFilter(opts.include ?? defaultIncludeRE, opts.exclude);
76
+ const devRuntime = `${opts.jsxImportSource ?? "react"}/jsx-dev-runtime`;
74
77
  let needHiresSourcemap = false;
75
78
  let isProduction = true;
76
79
  let projectRoot = process.cwd();
77
- let skipFastRefresh = opts.fastRefresh === false;
78
- let skipReactImport = false;
79
- let runPluginOverrides = (options, context) => false;
80
+ let skipFastRefresh = false;
81
+ let runPluginOverrides;
80
82
  let staticBabelOptions;
81
- const useAutomaticRuntime = opts.jsxRuntime !== "classic";
82
83
  const importReactRE = /(?:^|\n)import\s+(?:\*\s+as\s+)?React(?:,|\s+)/;
83
- const fileExtensionRE = /\.[^/\s?]+$/;
84
84
  const viteBabel = {
85
85
  name: "vite:react-babel",
86
86
  enforce: "pre",
87
- config(userConfig, { mode }) {
88
- const resolvedRoot = normalizePath(
89
- userConfig.root ? path.resolve(userConfig.root) : process.cwd()
90
- );
91
- const envDir = userConfig.envDir ? normalizePath(path.resolve(resolvedRoot, userConfig.envDir)) : resolvedRoot;
92
- loadEnv(mode, envDir, resolveEnvPrefix(userConfig));
93
- const isProduction2 = (process.env.NODE_ENV || process.env.VITE_USER_NODE_ENV || mode) === "production";
87
+ config() {
94
88
  if (opts.jsxRuntime === "classic") {
95
89
  return {
96
90
  esbuild: {
97
- logOverride: {
98
- "this-is-undefined-in-esm": "silent"
99
- },
100
- jsx: "transform",
101
- jsxImportSource: opts.jsxImportSource,
102
- jsxSideEffects: opts.jsxPure === false
91
+ jsx: "transform"
103
92
  }
104
93
  };
105
94
  } else {
106
95
  return {
107
96
  esbuild: {
108
- jsxDev: !isProduction2,
109
97
  jsx: "automatic",
110
- jsxImportSource: opts.jsxImportSource,
111
- jsxSideEffects: opts.jsxPure === false
98
+ jsxImportSource: opts.jsxImportSource
112
99
  }
113
100
  };
114
101
  }
@@ -116,155 +103,126 @@ function viteReact(opts = {}) {
116
103
  configResolved(config) {
117
104
  devBase = config.base;
118
105
  projectRoot = config.root;
119
- filter = createFilter(opts.include, opts.exclude, {
120
- resolve: projectRoot
121
- });
122
106
  needHiresSourcemap = config.command === "build" && !!config.build.sourcemap;
123
107
  isProduction = config.isProduction;
124
- skipFastRefresh || (skipFastRefresh = isProduction || config.command === "build");
125
- const jsxInject = config.esbuild && config.esbuild.jsxInject;
126
- if (jsxInject && importReactRE.test(jsxInject)) {
127
- skipReactImport = true;
128
- config.logger.warn(
129
- "[@vitejs/plugin-react] This plugin imports React for you automatically, so you can stop using `esbuild.jsxInject` for that purpose."
108
+ skipFastRefresh = isProduction || config.command === "build";
109
+ if (opts.jsxRuntime === "classic") {
110
+ config.logger.warnOnce(
111
+ "[@vitejs/plugin-react] Support for classic runtime is deprecated."
130
112
  );
131
113
  }
132
- config.plugins.forEach((plugin) => {
133
- const hasConflict = plugin.name === "react-refresh" || plugin !== viteReactJsx && plugin.name === "vite:react-jsx";
134
- if (hasConflict)
135
- return config.logger.warn(
136
- `[@vitejs/plugin-react] You should stop using "${plugin.name}" since this plugin conflicts with it.`
137
- );
138
- });
139
- runPluginOverrides = (babelOptions, context) => {
140
- const hooks = config.plugins.map((plugin) => plugin.api?.reactBabel).filter(Boolean);
141
- if (hooks.length > 0) {
142
- return (runPluginOverrides = (babelOptions2, context2) => {
143
- hooks.forEach((hook) => hook(babelOptions2, context2, config));
144
- return true;
145
- })(babelOptions, context);
146
- }
147
- runPluginOverrides = () => false;
148
- return false;
149
- };
114
+ if ("jsxPure" in opts) {
115
+ config.logger.warnOnce(
116
+ "[@vitejs/plugin-react] jsxPure was removed. You can configure esbuild.jsxSideEffects directly."
117
+ );
118
+ }
119
+ const hooks = config.plugins.map((plugin) => plugin.api?.reactBabel).filter(defined);
120
+ if (hooks.length > 0) {
121
+ runPluginOverrides = (babelOptions, context) => {
122
+ hooks.forEach((hook) => hook(babelOptions, context, config));
123
+ };
124
+ } else if (typeof opts.babel !== "function") {
125
+ staticBabelOptions = createBabelOptions(opts.babel);
126
+ }
150
127
  },
151
128
  async transform(code, id, options) {
129
+ if (id.includes("/node_modules/"))
130
+ return;
131
+ const [filepath] = id.split("?");
132
+ if (!filter(filepath))
133
+ return;
152
134
  const ssr = options?.ssr === true;
153
- const [filepath, querystring = ""] = id.split("?");
154
- const [extension = ""] = querystring.match(fileExtensionRE) || filepath.match(fileExtensionRE) || [];
155
- if (/\.(?:mjs|[tj]sx?)$/.test(extension)) {
156
- const isJSX = extension.endsWith("x");
157
- const isNodeModules = id.includes("/node_modules/");
158
- const isProjectFile = !isNodeModules && (id[0] === "\0" || id.startsWith(projectRoot + "/"));
159
- let babelOptions = staticBabelOptions;
160
- if (typeof opts.babel === "function") {
161
- const rawOptions = opts.babel(id, { ssr });
162
- babelOptions = createBabelOptions(rawOptions);
163
- runPluginOverrides(babelOptions, { ssr, id });
164
- } else if (!babelOptions) {
165
- babelOptions = createBabelOptions(opts.babel);
166
- if (!runPluginOverrides(babelOptions, { ssr, id })) {
167
- staticBabelOptions = babelOptions;
168
- }
169
- }
170
- const plugins = isProjectFile ? [...babelOptions.plugins] : [];
171
- let useFastRefresh = false;
172
- if (!skipFastRefresh && !ssr && !isNodeModules) {
173
- const isReactModule = isJSX || importReactRE.test(code);
174
- if (isReactModule && filter(id)) {
175
- useFastRefresh = true;
176
- plugins.push([
177
- await loadPlugin("react-refresh/babel"),
178
- { skipEnvCheck: true }
179
- ]);
180
- }
181
- }
182
- let prependReactImport = false;
183
- if (!isProjectFile || isJSX) {
184
- if (!useAutomaticRuntime && isProjectFile) {
185
- if (!isProduction) {
186
- plugins.push(
187
- await loadPlugin("@babel/plugin-transform-react-jsx-self"),
188
- await loadPlugin("@babel/plugin-transform-react-jsx-source")
189
- );
190
- }
191
- if (!skipReactImport && !importReactRE.test(code)) {
192
- prependReactImport = true;
193
- }
194
- }
195
- }
196
- let inputMap;
197
- if (prependReactImport) {
198
- if (needHiresSourcemap) {
199
- const s = new MagicString(code);
200
- s.prepend(prependReactImportCode);
201
- code = s.toString();
202
- inputMap = s.generateMap({ hires: true, source: id });
203
- } else {
204
- code = prependReactImportCode + code;
205
- }
206
- }
207
- const shouldSkip = !plugins.length && !babelOptions.configFile && !(isProjectFile && babelOptions.babelrc);
208
- if (shouldSkip) {
209
- return {
210
- code,
211
- map: inputMap ?? null
212
- };
135
+ const babelOptions = (() => {
136
+ if (staticBabelOptions)
137
+ return staticBabelOptions;
138
+ const newBabelOptions = createBabelOptions(
139
+ typeof opts.babel === "function" ? opts.babel(id, { ssr }) : opts.babel
140
+ );
141
+ runPluginOverrides?.(newBabelOptions, { id, ssr });
142
+ return newBabelOptions;
143
+ })();
144
+ const plugins = [...babelOptions.plugins];
145
+ const isJSX = filepath.endsWith("x");
146
+ const useFastRefresh = !skipFastRefresh && !ssr && (isJSX || (opts.jsxRuntime === "classic" ? code.includes(devRuntime) : importReactRE.test(code)));
147
+ if (useFastRefresh) {
148
+ plugins.push([
149
+ await loadPlugin("react-refresh/babel"),
150
+ { skipEnvCheck: true }
151
+ ]);
152
+ }
153
+ let prependReactImport = false;
154
+ if (opts.jsxRuntime === "classic" && isJSX) {
155
+ if (!isProduction) {
156
+ plugins.push(
157
+ await loadPlugin("@babel/plugin-transform-react-jsx-self"),
158
+ await loadPlugin("@babel/plugin-transform-react-jsx-source")
159
+ );
213
160
  }
214
- const parserPlugins = [
215
- ...babelOptions.parserOpts.plugins,
216
- "importMeta",
217
- // This plugin is applied before esbuild transforms the code,
218
- // so we need to enable some stage 3 syntax that is supported in
219
- // TypeScript and some environments already.
220
- "topLevelAwait",
221
- "classProperties",
222
- "classPrivateProperties",
223
- "classPrivateMethods"
224
- ];
225
- if (!extension.endsWith(".ts")) {
226
- parserPlugins.push("jsx");
161
+ if (!importReactRE.test(code)) {
162
+ prependReactImport = true;
227
163
  }
228
- if (/\.tsx?$/.test(extension)) {
229
- parserPlugins.push("typescript");
164
+ }
165
+ let inputMap;
166
+ if (prependReactImport) {
167
+ if (needHiresSourcemap) {
168
+ const s = new MagicString(code);
169
+ s.prepend(prependReactImportCode);
170
+ code = s.toString();
171
+ inputMap = s.generateMap({ hires: true, source: id });
172
+ } else {
173
+ code = prependReactImportCode + code;
230
174
  }
231
- const result = await babel.transformAsync(code, {
232
- ...babelOptions,
233
- root: projectRoot,
234
- filename: id,
235
- sourceFileName: filepath,
236
- parserOpts: {
237
- ...babelOptions.parserOpts,
238
- sourceType: "module",
239
- allowAwaitOutsideFunction: true,
240
- plugins: parserPlugins
241
- },
242
- generatorOpts: {
243
- ...babelOptions.generatorOpts,
244
- decoratorsBeforeExport: true
245
- },
246
- plugins,
247
- sourceMaps: true,
248
- // Vite handles sourcemap flattening
249
- inputSourceMap: inputMap ?? false
250
- });
251
- if (result) {
252
- let code2 = result.code;
253
- if (useFastRefresh && refreshContentRE.test(code2)) {
254
- code2 = addRefreshWrapper(code2, id);
255
- }
256
- return {
257
- code: code2,
258
- map: result.map
259
- };
175
+ }
176
+ if (!plugins.length && !babelOptions.configFile && !babelOptions.babelrc) {
177
+ return { code, map: inputMap ?? null };
178
+ }
179
+ const parserPlugins = [...babelOptions.parserOpts.plugins];
180
+ if (!filepath.endsWith(".ts")) {
181
+ parserPlugins.push("jsx");
182
+ }
183
+ if (tsRE.test(filepath)) {
184
+ parserPlugins.push("typescript");
185
+ }
186
+ const result = await babel.transformAsync(code, {
187
+ ...babelOptions,
188
+ root: projectRoot,
189
+ filename: id,
190
+ sourceFileName: filepath,
191
+ parserOpts: {
192
+ ...babelOptions.parserOpts,
193
+ sourceType: "module",
194
+ allowAwaitOutsideFunction: true,
195
+ plugins: parserPlugins
196
+ },
197
+ generatorOpts: {
198
+ ...babelOptions.generatorOpts,
199
+ decoratorsBeforeExport: true
200
+ },
201
+ plugins,
202
+ sourceMaps: true,
203
+ // Vite handles sourcemap flattening
204
+ inputSourceMap: inputMap ?? false
205
+ });
206
+ if (result) {
207
+ let code2 = result.code;
208
+ if (useFastRefresh && refreshContentRE.test(code2)) {
209
+ code2 = addRefreshWrapper(code2, id);
260
210
  }
211
+ return { code: code2, map: result.map };
261
212
  }
262
213
  }
263
214
  };
264
215
  const viteReactRefresh = {
265
216
  name: "vite:react-refresh",
266
217
  enforce: "pre",
267
- config: () => ({
218
+ config: (userConfig) => ({
219
+ build: silenceUseClientWarning(userConfig),
220
+ optimizeDeps: {
221
+ // We can't add `react-dom` because the dependency is `react-dom/client`
222
+ // for React 18 while it's `react-dom` for React 17. We'd need to detect
223
+ // what React version the user has installed.
224
+ include: ["react", devRuntime]
225
+ },
268
226
  resolve: {
269
227
  dedupe: ["react", "react-dom"]
270
228
  }
@@ -290,54 +248,35 @@ function viteReact(opts = {}) {
290
248
  ];
291
249
  }
292
250
  };
293
- const reactJsxRuntimeId = "react/jsx-runtime";
294
- const reactJsxDevRuntimeId = "react/jsx-dev-runtime";
295
- const virtualReactJsxRuntimeId = "\0" + reactJsxRuntimeId;
296
- const virtualReactJsxDevRuntimeId = "\0" + reactJsxDevRuntimeId;
297
- const viteReactJsx = {
298
- name: "vite:react-jsx",
299
- enforce: "pre",
300
- config() {
301
- return {
302
- optimizeDeps: {
303
- // We can't add `react-dom` because the dependency is `react-dom/client`
304
- // for React 18 while it's `react-dom` for React 17. We'd need to detect
305
- // what React version the user has installed.
306
- include: [reactJsxRuntimeId, reactJsxDevRuntimeId, "react"]
307
- }
308
- };
309
- },
310
- resolveId(id, importer) {
311
- if (id === reactJsxRuntimeId && importer !== virtualReactJsxRuntimeId) {
312
- return virtualReactJsxRuntimeId;
313
- }
314
- if (id === reactJsxDevRuntimeId && importer !== virtualReactJsxDevRuntimeId) {
315
- return virtualReactJsxDevRuntimeId;
316
- }
317
- },
318
- load(id) {
319
- if (id === virtualReactJsxRuntimeId) {
320
- return [
321
- `import * as jsxRuntime from ${JSON.stringify(reactJsxRuntimeId)}`,
322
- `export const Fragment = jsxRuntime.Fragment`,
323
- `export const jsx = jsxRuntime.jsx`,
324
- `export const jsxs = jsxRuntime.jsxs`
325
- ].join("\n");
251
+ return [viteBabel, viteReactRefresh];
252
+ }
253
+ viteReact.preambleCode = preambleCode;
254
+ const silenceUseClientWarning = (userConfig) => ({
255
+ rollupOptions: {
256
+ onwarn(warning, defaultHandler) {
257
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes("use client")) {
258
+ return;
326
259
  }
327
- if (id === virtualReactJsxDevRuntimeId) {
328
- return [
329
- `import * as jsxRuntime from ${JSON.stringify(reactJsxDevRuntimeId)}`,
330
- `export const Fragment = jsxRuntime.Fragment`,
331
- `export const jsxDEV = jsxRuntime.jsxDEV`
332
- ].join("\n");
260
+ if (userConfig.build?.rollupOptions?.onwarn) {
261
+ userConfig.build.rollupOptions.onwarn(warning, defaultHandler);
262
+ } else {
263
+ defaultHandler(warning);
333
264
  }
334
265
  }
335
- };
336
- return [viteBabel, viteReactRefresh, useAutomaticRuntime && viteReactJsx];
337
- }
338
- viteReact.preambleCode = preambleCode;
339
- function loadPlugin(path2) {
340
- return import(path2).then((module) => module.default || module);
266
+ }
267
+ });
268
+ const loadedPlugin = /* @__PURE__ */ new Map();
269
+ function loadPlugin(path) {
270
+ const cached = loadedPlugin.get(path);
271
+ if (cached)
272
+ return cached;
273
+ const promise = import(path).then((module) => {
274
+ const value = module.default || module;
275
+ loadedPlugin.set(path, value);
276
+ return value;
277
+ });
278
+ loadedPlugin.set(path, promise);
279
+ return promise;
341
280
  }
342
281
  function createBabelOptions(rawOptions) {
343
282
  var _a;
@@ -353,5 +292,8 @@ function createBabelOptions(rawOptions) {
353
292
  (_a = babelOptions.parserOpts).plugins || (_a.plugins = []);
354
293
  return babelOptions;
355
294
  }
295
+ function defined(value) {
296
+ return value !== void 0;
297
+ }
356
298
 
357
299
  export { viteReact as default };
@@ -1,3 +1,13 @@
1
+ /* eslint-disable no-undef */
2
+ if (typeof window !== 'undefined') {
3
+ if (window.__vite_plugin_react_runtime_loaded__) {
4
+ throw new Error(
5
+ 'React refresh runtime was loaded twice. Maybe you forgot the base path?',
6
+ )
7
+ }
8
+ window.__vite_plugin_react_runtime_loaded__ = true
9
+ }
10
+
1
11
  function debounce(fn, delay) {
2
12
  let handle
3
13
  return () => {
@@ -6,7 +16,6 @@ function debounce(fn, delay) {
6
16
  }
7
17
  }
8
18
 
9
- /* eslint-disable no-undef */
10
19
  const enqueueUpdate = debounce(exports.performReactRefresh, 16)
11
20
 
12
21
  // Taken from https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/lib/runtime/RefreshUtils.js#L141
@@ -16,7 +25,11 @@ function registerExportsForReactRefresh(filename, moduleExports) {
16
25
  if (key === '__esModule') continue
17
26
  const exportValue = moduleExports[key]
18
27
  if (exports.isLikelyComponentType(exportValue)) {
19
- exports.register(exportValue, filename + ' ' + key)
28
+ // 'export' is required to avoid key collision when renamed exports that
29
+ // shadow a local component name: https://github.com/vitejs/vite-plugin-react/issues/116
30
+ // The register function has an identity check to not register twice the same component,
31
+ // so this is safe to not used the same key here.
32
+ exports.register(exportValue, filename + ' export ' + key)
20
33
  }
21
34
  }
22
35
  }
@@ -53,6 +66,13 @@ function predicateOnExport(moduleExports, predicate) {
53
66
  return true
54
67
  }
55
68
 
69
+ // Hides vite-ignored dynamic import so that Vite can skip analysis if no other
70
+ // dynamic import is present (https://github.com/vitejs/vite/pull/12732)
71
+ function __hmr_import(module) {
72
+ return import(/* @vite-ignore */ module)
73
+ }
74
+
75
+ exports.__hmr_import = __hmr_import
56
76
  exports.registerExportsForReactRefresh = registerExportsForReactRefresh
57
77
  exports.validateRefreshBoundaryAndEnqueueUpdate =
58
78
  validateRefreshBoundaryAndEnqueueUpdate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitejs/plugin-react",
3
- "version": "3.1.0",
3
+ "version": "4.0.0-beta.1",
4
4
  "license": "MIT",
5
5
  "author": "Evan You",
6
6
  "contributors": [
@@ -39,13 +39,13 @@
39
39
  },
40
40
  "homepage": "https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme",
41
41
  "dependencies": {
42
- "@babel/core": "^7.20.12",
43
- "@babel/plugin-transform-react-jsx-self": "^7.18.6",
42
+ "@babel/core": "^7.21.4",
43
+ "@babel/plugin-transform-react-jsx-self": "^7.21.0",
44
44
  "@babel/plugin-transform-react-jsx-source": "^7.19.6",
45
- "magic-string": "^0.27.0",
45
+ "magic-string": "^0.30.0",
46
46
  "react-refresh": "^0.14.0"
47
47
  },
48
48
  "peerDependencies": {
49
- "vite": "^4.1.0-beta.0"
49
+ "vite": "^4.2.0"
50
50
  }
51
51
  }