diff --git a/packages/plugin-react/package.json b/packages/plugin-react/package.json
index bb0d146594d900..c21207996e4089 100644
--- a/packages/plugin-react/package.json
+++ b/packages/plugin-react/package.json
@@ -44,6 +44,7 @@
"@babel/plugin-transform-react-jsx-development": "^7.18.6",
"@babel/plugin-transform-react-jsx-self": "^7.18.6",
"@babel/plugin-transform-react-jsx-source": "^7.18.6",
+ "magic-string": "^0.26.2",
"react-refresh": "^0.14.0"
},
"peerDependencies": {
diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts
index 160e9ca1d2f883..f1adf81510bcef 100644
--- a/packages/plugin-react/src/index.ts
+++ b/packages/plugin-react/src/index.ts
@@ -3,6 +3,8 @@ import type { ParserOptions, TransformOptions, types as t } from '@babel/core'
import * as babel from '@babel/core'
import { createFilter, normalizePath } from 'vite'
import type { Plugin, PluginOption, ResolvedConfig } from 'vite'
+import MagicString from 'magic-string'
+import type { SourceMap } from 'magic-string'
import {
addRefreshWrapper,
isRefreshBoundary,
@@ -88,11 +90,14 @@ declare module 'vite' {
}
}
+const prependReactImportCode = "import React from 'react'; "
+
export default function viteReact(opts: Options = {}): PluginOption[] {
// Provide default values for Rollup compat.
let devBase = '/'
let resolvedCacheDir: string
let filter = createFilter(opts.include, opts.exclude)
+ let needHiresSourcemap = false
let isProduction = true
let projectRoot = process.cwd()
let skipFastRefresh = opts.fastRefresh === false
@@ -135,6 +140,8 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
filter = createFilter(opts.include, opts.exclude, {
resolve: projectRoot
})
+ needHiresSourcemap =
+ config.command === 'build' && !!config.build.sourcemap
isProduction = config.isProduction
skipFastRefresh ||= isProduction || config.command === 'build'
@@ -217,6 +224,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
}
let ast: t.File | null | undefined
+ let prependReactImport = false
if (!isProjectFile || isJSX) {
if (useAutomaticRuntime) {
// By reverse-compiling "React.createElement" calls into JSX,
@@ -261,11 +269,23 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
// Even if the automatic JSX runtime is not used, we can still
// inject the React import for .jsx and .tsx modules.
if (!skipReactImport && !importReactRE.test(code)) {
- code = `import React from 'react'; ` + code
+ prependReactImport = true
}
}
}
+ let inputMap: SourceMap | undefined
+ if (prependReactImport) {
+ if (needHiresSourcemap) {
+ const s = new MagicString(code)
+ s.prepend(prependReactImportCode)
+ code = s.toString()
+ inputMap = s.generateMap({ hires: true, source: id })
+ } else {
+ code = prependReactImportCode + code
+ }
+ }
+
// Plugins defined through this Vite plugin are only applied
// to modules within the project root, but "babel.config.js"
// files can define plugins that need to be applied to every
@@ -275,10 +295,11 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
!babelOptions.configFile &&
!(isProjectFile && babelOptions.babelrc)
+ // Avoid parsing if no plugins exist.
if (shouldSkip) {
- // Avoid parsing if no plugins exist.
return {
- code
+ code,
+ map: inputMap ?? null
}
}
@@ -326,7 +347,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
plugins,
sourceMaps: true,
// Vite handles sourcemap flattening
- inputSourceMap: false as any
+ inputSourceMap: inputMap ?? (false as any)
})
if (result) {
diff --git a/playground/react-sourcemap/App.jsx b/playground/react-sourcemap/App.jsx
new file mode 100644
index 00000000000000..ec47ca46ad212e
--- /dev/null
+++ b/playground/react-sourcemap/App.jsx
@@ -0,0 +1,8 @@
+console.log('App.jsx 1') // for sourcemap
+function App() {
+ return
foo
+}
+
+console.log('App.jsx 2') // for sourcemap
+
+export default App
diff --git a/playground/react-sourcemap/__tests__/react-sourcemap.spec.ts b/playground/react-sourcemap/__tests__/react-sourcemap.spec.ts
new file mode 100644
index 00000000000000..a1d35485760753
--- /dev/null
+++ b/playground/react-sourcemap/__tests__/react-sourcemap.spec.ts
@@ -0,0 +1,7 @@
+import { isBuild, serverLogs } from '~utils'
+
+test.runIf(isBuild)('should not output sourcemap warning', () => {
+ serverLogs.forEach((log) => {
+ expect(log).not.toMatch('Sourcemap is likely to be incorrect')
+ })
+})
diff --git a/playground/react-sourcemap/index.html b/playground/react-sourcemap/index.html
new file mode 100644
index 00000000000000..d3ca9807c608ba
--- /dev/null
+++ b/playground/react-sourcemap/index.html
@@ -0,0 +1,2 @@
+
+
diff --git a/playground/react-sourcemap/main.jsx b/playground/react-sourcemap/main.jsx
new file mode 100644
index 00000000000000..705d3340097aeb
--- /dev/null
+++ b/playground/react-sourcemap/main.jsx
@@ -0,0 +1,9 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+
+ReactDOM.createRoot(document.getElementById('app')).render(
+ React.createElement(App)
+)
+
+console.log('main.jsx') // for sourcemap
diff --git a/playground/react-sourcemap/package.json b/playground/react-sourcemap/package.json
new file mode 100644
index 00000000000000..91aa3331b877b4
--- /dev/null
+++ b/playground/react-sourcemap/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "test-react-sourcemap",
+ "private": true,
+ "version": "0.0.0",
+ "scripts": {
+ "dev": "vite",
+ "dev:classic": "cross-env USE_CLASSIC=1 vite",
+ "build": "vite build",
+ "build:classic": "cross-env USE_CLASSIC=1 vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "workspace:*",
+ "cross-env": "^7.0.3"
+ }
+}
diff --git a/playground/react-sourcemap/vite.config.ts b/playground/react-sourcemap/vite.config.ts
new file mode 100644
index 00000000000000..d8a2cc46b419b9
--- /dev/null
+++ b/playground/react-sourcemap/vite.config.ts
@@ -0,0 +1,15 @@
+import react from '@vitejs/plugin-react'
+import type { UserConfig } from 'vite'
+
+const config: UserConfig = {
+ plugins: [
+ react({
+ jsxRuntime: process.env.USE_CLASSIC === '1' ? 'classic' : 'automatic'
+ })
+ ],
+ build: {
+ sourcemap: true
+ }
+}
+
+export default config
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a9b329c87ac56d..c6a4178e50ab9c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -162,6 +162,7 @@ importers:
'@babel/plugin-transform-react-jsx-development': ^7.18.6
'@babel/plugin-transform-react-jsx-self': ^7.18.6
'@babel/plugin-transform-react-jsx-source': ^7.18.6
+ magic-string: ^0.26.2
react-refresh: ^0.14.0
vite: workspace:*
dependencies:
@@ -170,6 +171,7 @@ importers:
'@babel/plugin-transform-react-jsx-development': 7.18.6_@babel+core@7.18.6
'@babel/plugin-transform-react-jsx-self': 7.18.6_@babel+core@7.18.6
'@babel/plugin-transform-react-jsx-source': 7.18.6_@babel+core@7.18.6
+ magic-string: 0.26.2
react-refresh: 0.14.0
devDependencies:
vite: link:../vite
@@ -821,6 +823,19 @@ importers:
'@emotion/babel-plugin': 11.9.2
'@vitejs/plugin-react': link:../../packages/plugin-react
+ playground/react-sourcemap:
+ specifiers:
+ '@vitejs/plugin-react': workspace:*
+ cross-env: ^7.0.3
+ react: ^18.2.0
+ react-dom: ^18.2.0
+ dependencies:
+ react: 18.2.0
+ react-dom: 18.2.0_react@18.2.0
+ devDependencies:
+ '@vitejs/plugin-react': link:../../packages/plugin-react
+ cross-env: 7.0.3
+
playground/react/jsx-entry:
specifiers: {}
@@ -5710,7 +5725,7 @@ packages:
dev: true
/isexe/2.0.0:
- resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=}
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
/jiti/1.13.0: