@vitejs/plugin-react 3.1.0 → 4.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/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. For TS projects this is inferred from the tsconfig. If you have some React code outside JSX/TSX files, this will be used to detect the presence of React code and apply Fast Refresh.
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,33 @@ 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);
89
97
  let needHiresSourcemap = false;
90
98
  let isProduction = true;
91
99
  let projectRoot = process.cwd();
92
- let skipFastRefresh = opts.fastRefresh === false;
93
- let skipReactImport = false;
94
- let runPluginOverrides = (options, context) => false;
100
+ let skipFastRefresh = false;
101
+ let runPluginOverrides;
95
102
  let staticBabelOptions;
96
- const useAutomaticRuntime = opts.jsxRuntime !== "classic";
97
103
  const importReactRE = /(?:^|\n)import\s+(?:\*\s+as\s+)?React(?:,|\s+)/;
98
- const fileExtensionRE = /\.[^/\s?]+$/;
99
104
  const viteBabel = {
100
105
  name: "vite:react-babel",
101
106
  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";
107
+ config() {
109
108
  if (opts.jsxRuntime === "classic") {
110
109
  return {
111
110
  esbuild: {
112
- logOverride: {
113
- "this-is-undefined-in-esm": "silent"
114
- },
115
- jsx: "transform",
116
- jsxImportSource: opts.jsxImportSource,
117
- jsxSideEffects: opts.jsxPure === false
111
+ jsx: "transform"
118
112
  }
119
113
  };
120
114
  } else {
121
115
  return {
122
116
  esbuild: {
123
- jsxDev: !isProduction2,
124
117
  jsx: "automatic",
125
- jsxImportSource: opts.jsxImportSource,
126
- jsxSideEffects: opts.jsxPure === false
118
+ jsxImportSource: opts.jsxImportSource
127
119
  }
128
120
  };
129
121
  }
@@ -131,155 +123,128 @@ function viteReact(opts = {}) {
131
123
  configResolved(config) {
132
124
  devBase = config.base;
133
125
  projectRoot = config.root;
134
- filter = vite.createFilter(opts.include, opts.exclude, {
135
- resolve: projectRoot
136
- });
137
126
  needHiresSourcemap = config.command === "build" && !!config.build.sourcemap;
138
127
  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."
128
+ skipFastRefresh = isProduction || config.command === "build";
129
+ if (opts.jsxRuntime === "classic") {
130
+ config.logger.warnOnce(
131
+ "[@vitejs/plugin-react] Support for classic runtime is deprecated."
145
132
  );
146
133
  }
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
- };
134
+ if ("jsxPure" in opts) {
135
+ config.logger.warnOnce(
136
+ "[@vitejs/plugin-react] jsxPure was removed. You can configure esbuild.jsxSideEffects directly."
137
+ );
138
+ }
139
+ const hooks = config.plugins.map((plugin) => plugin.api?.reactBabel).filter(defined);
140
+ if (hooks.length > 0) {
141
+ runPluginOverrides = (babelOptions, context) => {
142
+ hooks.forEach((hook) => hook(babelOptions, context, config));
143
+ };
144
+ } else if (typeof opts.babel !== "function") {
145
+ staticBabelOptions = createBabelOptions(opts.babel);
146
+ }
165
147
  },
166
148
  async transform(code, id, options) {
149
+ if (id.includes("/node_modules/"))
150
+ return;
151
+ const [filepath] = id.split("?");
152
+ if (!filter(filepath))
153
+ return;
167
154
  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
- };
155
+ const babelOptions = (() => {
156
+ if (staticBabelOptions)
157
+ return staticBabelOptions;
158
+ const newBabelOptions = createBabelOptions(
159
+ typeof opts.babel === "function" ? opts.babel(id, { ssr }) : opts.babel
160
+ );
161
+ runPluginOverrides?.(newBabelOptions, { id, ssr });
162
+ return newBabelOptions;
163
+ })();
164
+ const plugins = [...babelOptions.plugins];
165
+ const isJSX = filepath.endsWith("x");
166
+ const useFastRefresh = !skipFastRefresh && !ssr && (isJSX || (opts.jsxRuntime === "classic" ? code.includes(
167
+ `${opts.jsxImportSource ?? "react"}/jsx-dev-runtime`
168
+ ) : importReactRE.test(code)));
169
+ if (useFastRefresh) {
170
+ plugins.push([
171
+ await loadPlugin("react-refresh/babel"),
172
+ { skipEnvCheck: true }
173
+ ]);
174
+ }
175
+ let prependReactImport = false;
176
+ if (opts.jsxRuntime === "classic" && isJSX) {
177
+ if (!isProduction) {
178
+ plugins.push(
179
+ await loadPlugin("@babel/plugin-transform-react-jsx-self"),
180
+ await loadPlugin("@babel/plugin-transform-react-jsx-source")
181
+ );
228
182
  }
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");
183
+ if (!importReactRE.test(code)) {
184
+ prependReactImport = true;
242
185
  }
243
- if (/\.tsx?$/.test(extension)) {
244
- parserPlugins.push("typescript");
186
+ }
187
+ let inputMap;
188
+ if (prependReactImport) {
189
+ if (needHiresSourcemap) {
190
+ const s = new MagicString__default(code);
191
+ s.prepend(prependReactImportCode);
192
+ code = s.toString();
193
+ inputMap = s.generateMap({ hires: true, source: id });
194
+ } else {
195
+ code = prependReactImportCode + code;
245
196
  }
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
- };
197
+ }
198
+ if (!plugins.length && !babelOptions.configFile && !babelOptions.babelrc) {
199
+ return { code, map: inputMap ?? null };
200
+ }
201
+ const parserPlugins = [...babelOptions.parserOpts.plugins];
202
+ if (!filepath.endsWith(".ts")) {
203
+ parserPlugins.push("jsx");
204
+ }
205
+ if (tsRE.test(filepath)) {
206
+ parserPlugins.push("typescript");
207
+ }
208
+ const result = await babel__namespace.transformAsync(code, {
209
+ ...babelOptions,
210
+ root: projectRoot,
211
+ filename: id,
212
+ sourceFileName: filepath,
213
+ parserOpts: {
214
+ ...babelOptions.parserOpts,
215
+ sourceType: "module",
216
+ allowAwaitOutsideFunction: true,
217
+ plugins: parserPlugins
218
+ },
219
+ generatorOpts: {
220
+ ...babelOptions.generatorOpts,
221
+ decoratorsBeforeExport: true
222
+ },
223
+ plugins,
224
+ sourceMaps: true,
225
+ // Vite handles sourcemap flattening
226
+ inputSourceMap: inputMap ?? false
227
+ });
228
+ if (result) {
229
+ let code2 = result.code;
230
+ if (useFastRefresh && refreshContentRE.test(code2)) {
231
+ code2 = addRefreshWrapper(code2, id);
275
232
  }
233
+ return { code: code2, map: result.map };
276
234
  }
277
235
  }
278
236
  };
279
237
  const viteReactRefresh = {
280
238
  name: "vite:react-refresh",
281
239
  enforce: "pre",
282
- config: () => ({
240
+ config: (userConfig) => ({
241
+ build: silenceUseClientWarning(userConfig),
242
+ optimizeDeps: {
243
+ // We can't add `react-dom` because the dependency is `react-dom/client`
244
+ // for React 18 while it's `react-dom` for React 17. We'd need to detect
245
+ // what React version the user has installed.
246
+ include: ["react"]
247
+ },
283
248
  resolve: {
284
249
  dedupe: ["react", "react-dom"]
285
250
  }
@@ -305,54 +270,35 @@ function viteReact(opts = {}) {
305
270
  ];
306
271
  }
307
272
  };
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");
273
+ return [viteBabel, viteReactRefresh];
274
+ }
275
+ viteReact.preambleCode = preambleCode;
276
+ const silenceUseClientWarning = (userConfig) => ({
277
+ rollupOptions: {
278
+ onwarn(warning, defaultHandler) {
279
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes("use client")) {
280
+ return;
341
281
  }
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");
282
+ if (userConfig.build?.rollupOptions?.onwarn) {
283
+ userConfig.build.rollupOptions.onwarn(warning, defaultHandler);
284
+ } else {
285
+ defaultHandler(warning);
348
286
  }
349
287
  }
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);
288
+ }
289
+ });
290
+ const loadedPlugin = /* @__PURE__ */ new Map();
291
+ function loadPlugin(path) {
292
+ const cached = loadedPlugin.get(path);
293
+ if (cached)
294
+ return cached;
295
+ const promise = import(path).then((module) => {
296
+ const value = module.default || module;
297
+ loadedPlugin.set(path, value);
298
+ return value;
299
+ });
300
+ loadedPlugin.set(path, promise);
301
+ return promise;
356
302
  }
357
303
  function createBabelOptions(rawOptions) {
358
304
  var _a;
@@ -368,6 +314,9 @@ function createBabelOptions(rawOptions) {
368
314
  (_a = babelOptions.parserOpts).plugins || (_a.plugins = []);
369
315
  return babelOptions;
370
316
  }
317
+ function defined(value) {
318
+ return value !== void 0;
319
+ }
371
320
 
372
321
  module.exports = viteReact;
373
322
  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
+ * For TS projects this is read from tsconfig
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,33 @@ 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);
74
76
  let needHiresSourcemap = false;
75
77
  let isProduction = true;
76
78
  let projectRoot = process.cwd();
77
- let skipFastRefresh = opts.fastRefresh === false;
78
- let skipReactImport = false;
79
- let runPluginOverrides = (options, context) => false;
79
+ let skipFastRefresh = false;
80
+ let runPluginOverrides;
80
81
  let staticBabelOptions;
81
- const useAutomaticRuntime = opts.jsxRuntime !== "classic";
82
82
  const importReactRE = /(?:^|\n)import\s+(?:\*\s+as\s+)?React(?:,|\s+)/;
83
- const fileExtensionRE = /\.[^/\s?]+$/;
84
83
  const viteBabel = {
85
84
  name: "vite:react-babel",
86
85
  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";
86
+ config() {
94
87
  if (opts.jsxRuntime === "classic") {
95
88
  return {
96
89
  esbuild: {
97
- logOverride: {
98
- "this-is-undefined-in-esm": "silent"
99
- },
100
- jsx: "transform",
101
- jsxImportSource: opts.jsxImportSource,
102
- jsxSideEffects: opts.jsxPure === false
90
+ jsx: "transform"
103
91
  }
104
92
  };
105
93
  } else {
106
94
  return {
107
95
  esbuild: {
108
- jsxDev: !isProduction2,
109
96
  jsx: "automatic",
110
- jsxImportSource: opts.jsxImportSource,
111
- jsxSideEffects: opts.jsxPure === false
97
+ jsxImportSource: opts.jsxImportSource
112
98
  }
113
99
  };
114
100
  }
@@ -116,155 +102,128 @@ function viteReact(opts = {}) {
116
102
  configResolved(config) {
117
103
  devBase = config.base;
118
104
  projectRoot = config.root;
119
- filter = createFilter(opts.include, opts.exclude, {
120
- resolve: projectRoot
121
- });
122
105
  needHiresSourcemap = config.command === "build" && !!config.build.sourcemap;
123
106
  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."
107
+ skipFastRefresh = isProduction || config.command === "build";
108
+ if (opts.jsxRuntime === "classic") {
109
+ config.logger.warnOnce(
110
+ "[@vitejs/plugin-react] Support for classic runtime is deprecated."
130
111
  );
131
112
  }
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
- };
113
+ if ("jsxPure" in opts) {
114
+ config.logger.warnOnce(
115
+ "[@vitejs/plugin-react] jsxPure was removed. You can configure esbuild.jsxSideEffects directly."
116
+ );
117
+ }
118
+ const hooks = config.plugins.map((plugin) => plugin.api?.reactBabel).filter(defined);
119
+ if (hooks.length > 0) {
120
+ runPluginOverrides = (babelOptions, context) => {
121
+ hooks.forEach((hook) => hook(babelOptions, context, config));
122
+ };
123
+ } else if (typeof opts.babel !== "function") {
124
+ staticBabelOptions = createBabelOptions(opts.babel);
125
+ }
150
126
  },
151
127
  async transform(code, id, options) {
128
+ if (id.includes("/node_modules/"))
129
+ return;
130
+ const [filepath] = id.split("?");
131
+ if (!filter(filepath))
132
+ return;
152
133
  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
- };
134
+ const babelOptions = (() => {
135
+ if (staticBabelOptions)
136
+ return staticBabelOptions;
137
+ const newBabelOptions = createBabelOptions(
138
+ typeof opts.babel === "function" ? opts.babel(id, { ssr }) : opts.babel
139
+ );
140
+ runPluginOverrides?.(newBabelOptions, { id, ssr });
141
+ return newBabelOptions;
142
+ })();
143
+ const plugins = [...babelOptions.plugins];
144
+ const isJSX = filepath.endsWith("x");
145
+ const useFastRefresh = !skipFastRefresh && !ssr && (isJSX || (opts.jsxRuntime === "classic" ? code.includes(
146
+ `${opts.jsxImportSource ?? "react"}/jsx-dev-runtime`
147
+ ) : importReactRE.test(code)));
148
+ if (useFastRefresh) {
149
+ plugins.push([
150
+ await loadPlugin("react-refresh/babel"),
151
+ { skipEnvCheck: true }
152
+ ]);
153
+ }
154
+ let prependReactImport = false;
155
+ if (opts.jsxRuntime === "classic" && isJSX) {
156
+ if (!isProduction) {
157
+ plugins.push(
158
+ await loadPlugin("@babel/plugin-transform-react-jsx-self"),
159
+ await loadPlugin("@babel/plugin-transform-react-jsx-source")
160
+ );
213
161
  }
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");
162
+ if (!importReactRE.test(code)) {
163
+ prependReactImport = true;
227
164
  }
228
- if (/\.tsx?$/.test(extension)) {
229
- parserPlugins.push("typescript");
165
+ }
166
+ let inputMap;
167
+ if (prependReactImport) {
168
+ if (needHiresSourcemap) {
169
+ const s = new MagicString(code);
170
+ s.prepend(prependReactImportCode);
171
+ code = s.toString();
172
+ inputMap = s.generateMap({ hires: true, source: id });
173
+ } else {
174
+ code = prependReactImportCode + code;
230
175
  }
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
- };
176
+ }
177
+ if (!plugins.length && !babelOptions.configFile && !babelOptions.babelrc) {
178
+ return { code, map: inputMap ?? null };
179
+ }
180
+ const parserPlugins = [...babelOptions.parserOpts.plugins];
181
+ if (!filepath.endsWith(".ts")) {
182
+ parserPlugins.push("jsx");
183
+ }
184
+ if (tsRE.test(filepath)) {
185
+ parserPlugins.push("typescript");
186
+ }
187
+ const result = await babel.transformAsync(code, {
188
+ ...babelOptions,
189
+ root: projectRoot,
190
+ filename: id,
191
+ sourceFileName: filepath,
192
+ parserOpts: {
193
+ ...babelOptions.parserOpts,
194
+ sourceType: "module",
195
+ allowAwaitOutsideFunction: true,
196
+ plugins: parserPlugins
197
+ },
198
+ generatorOpts: {
199
+ ...babelOptions.generatorOpts,
200
+ decoratorsBeforeExport: true
201
+ },
202
+ plugins,
203
+ sourceMaps: true,
204
+ // Vite handles sourcemap flattening
205
+ inputSourceMap: inputMap ?? false
206
+ });
207
+ if (result) {
208
+ let code2 = result.code;
209
+ if (useFastRefresh && refreshContentRE.test(code2)) {
210
+ code2 = addRefreshWrapper(code2, id);
260
211
  }
212
+ return { code: code2, map: result.map };
261
213
  }
262
214
  }
263
215
  };
264
216
  const viteReactRefresh = {
265
217
  name: "vite:react-refresh",
266
218
  enforce: "pre",
267
- config: () => ({
219
+ config: (userConfig) => ({
220
+ build: silenceUseClientWarning(userConfig),
221
+ optimizeDeps: {
222
+ // We can't add `react-dom` because the dependency is `react-dom/client`
223
+ // for React 18 while it's `react-dom` for React 17. We'd need to detect
224
+ // what React version the user has installed.
225
+ include: ["react"]
226
+ },
268
227
  resolve: {
269
228
  dedupe: ["react", "react-dom"]
270
229
  }
@@ -290,54 +249,35 @@ function viteReact(opts = {}) {
290
249
  ];
291
250
  }
292
251
  };
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");
252
+ return [viteBabel, viteReactRefresh];
253
+ }
254
+ viteReact.preambleCode = preambleCode;
255
+ const silenceUseClientWarning = (userConfig) => ({
256
+ rollupOptions: {
257
+ onwarn(warning, defaultHandler) {
258
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes("use client")) {
259
+ return;
326
260
  }
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");
261
+ if (userConfig.build?.rollupOptions?.onwarn) {
262
+ userConfig.build.rollupOptions.onwarn(warning, defaultHandler);
263
+ } else {
264
+ defaultHandler(warning);
333
265
  }
334
266
  }
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);
267
+ }
268
+ });
269
+ const loadedPlugin = /* @__PURE__ */ new Map();
270
+ function loadPlugin(path) {
271
+ const cached = loadedPlugin.get(path);
272
+ if (cached)
273
+ return cached;
274
+ const promise = import(path).then((module) => {
275
+ const value = module.default || module;
276
+ loadedPlugin.set(path, value);
277
+ return value;
278
+ });
279
+ loadedPlugin.set(path, promise);
280
+ return promise;
341
281
  }
342
282
  function createBabelOptions(rawOptions) {
343
283
  var _a;
@@ -353,5 +293,8 @@ function createBabelOptions(rawOptions) {
353
293
  (_a = babelOptions.parserOpts).plugins || (_a.plugins = []);
354
294
  return babelOptions;
355
295
  }
296
+ function defined(value) {
297
+ return value !== void 0;
298
+ }
356
299
 
357
300
  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.0",
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
  }