Skip to content

Commit 7a40659

Browse files
authored
fix(react): $RefreshSig$ is not defined with NODE_ENV=production vite dev (#1283)
1 parent d4ac6e6 commit 7a40659

8 files changed

Lines changed: 135 additions & 155 deletions

File tree

packages/plugin-react/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
### Fixed `$RefreshSig$ is not defined` error when running `vite dev` with `NODE_ENV=production`
6+
7+
When running `vite dev` with `NODE_ENV=production`, the app errored with `$RefreshSig$ is not defined`.
8+
This error is now fixed.
9+
510
## 6.0.3 (2026-06-23)
611

712
## 6.0.2 (2026-05-14)

packages/plugin-react/src/index.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
silenceUseClientWarning,
1313
virtualPreamblePlugin,
1414
} from '@vitejs/react-common'
15-
import type { Plugin } from 'vite'
15+
import type { Plugin, ServerOptions } from 'vite'
1616
import { reactRefreshWrapperPlugin } from 'vite/internal'
1717
import { reactCompilerPreset } from './reactCompilerPreset'
1818

@@ -66,11 +66,18 @@ export default function viteReact(opts: Options = {}): Plugin[] {
6666
const jsxImportDevRuntime = `${jsxImportSource}/jsx-dev-runtime`
6767

6868
let runningInVite = false
69-
let isProduction = true
7069
let skipFastRefresh = true
7170
let base: string
7271
let isBundledDev = false
7372

73+
function calculateSkipFastRefresh(
74+
isProduction: boolean,
75+
command: 'serve' | 'build',
76+
hmr: ServerOptions['hmr'],
77+
) {
78+
return isProduction || command === 'build' || hmr === false
79+
}
80+
7481
const viteBabel: Plugin = {
7582
name: 'vite:react-babel',
7683
enforce: 'pre',
@@ -109,11 +116,18 @@ export default function viteReact(opts: Options = {}): Plugin[] {
109116
if (config.experimental.bundledDev) {
110117
isBundledDev = true
111118
}
112-
isProduction = config.isProduction
113-
skipFastRefresh =
114-
isProduction ||
115-
config.command === 'build' ||
116-
config.server.hmr === false
119+
if (
120+
skipFastRefresh !==
121+
calculateSkipFastRefresh(
122+
config.isProduction,
123+
config.command,
124+
config.server?.hmr,
125+
)
126+
) {
127+
this.warn(
128+
`NODE_ENV (${JSON.stringify(process.env.NODE_ENV)}) or server.hmr was changed by plugins after the react plugin read the config. This may cause unexpected behavior.`,
129+
)
130+
}
117131
},
118132
options(options) {
119133
if (!runningInVite) {
@@ -148,8 +162,14 @@ export default function viteReact(opts: Options = {}): Plugin[] {
148162
const viteConfigPost: Plugin = {
149163
name: 'vite:react:config-post',
150164
enforce: 'post',
151-
config(userConfig) {
152-
if (userConfig.server?.hmr === false) {
165+
config(userConfig, { command }) {
166+
skipFastRefresh = calculateSkipFastRefresh(
167+
// same with ResolvedConfig.isProduction
168+
process.env.NODE_ENV === 'production',
169+
command,
170+
userConfig.server?.hmr,
171+
)
172+
if (skipFastRefresh) {
153173
return {
154174
oxc: {
155175
jsx: {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useState } from 'react'
2+
3+
export default function App() {
4+
const [count, setCount] = useState(0)
5+
return (
6+
<button onClick={() => setCount((count) => count + 1)}>
7+
count is {count}
8+
</button>
9+
)
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { expect, test } from 'vitest'
2+
import { page } from '~utils'
3+
4+
test('app works with NODE_ENV=production', async () => {
5+
expect(await page.textContent('button')).toMatch('count is 0')
6+
await page.click('button')
7+
expect(await page.textContent('button')).toMatch('count is 1')
8+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div id="app"></div>
2+
<script type="module">
3+
import React from 'react'
4+
import ReactDOM from 'react-dom/client'
5+
import App from './App.jsx'
6+
7+
ReactDOM.createRoot(document.getElementById('app')).render(
8+
React.createElement(App),
9+
)
10+
</script>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "@vitejs/test-react-node-env-production",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite",
7+
"build": "vite build",
8+
"debug": "node --inspect-brk vite",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"react": "^19.2.7",
13+
"react-dom": "^19.2.7"
14+
},
15+
"devDependencies": {
16+
"@vitejs/plugin-react": "workspace:*"
17+
}
18+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import react from '@vitejs/plugin-react'
2+
import type { UserConfig } from 'vite'
3+
4+
process.env.NODE_ENV = 'production'
5+
6+
const config: UserConfig = {
7+
server: { port: 8911 /* Should be unique */ },
8+
plugins: [react()],
9+
build: {
10+
// to make tests faster
11+
minify: false,
12+
},
13+
}
14+
15+
export default config

0 commit comments

Comments
 (0)