Skip to content

Commit e060b8f

Browse files
authored
fix: disable server HMR before plugins read it in their config hook (#10731)
1 parent 1c0ec34 commit e060b8f

4 files changed

Lines changed: 81 additions & 39 deletions

File tree

packages/vitest/src/node/config/resolveConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,7 @@ export async function resolveConfig(
10291029
mode: options.mode || 'test',
10301030
plugins: [
10311031
CliOverride(cliOptionsCopy),
1032-
VitestConfigServer(pluginsHarness),
1032+
...VitestConfigServer(pluginsHarness),
10331033
...VitestConfig(pluginsHarness),
10341034
...VitestCorePlugin(pluginsHarness, options),
10351035
...BrowserLoaderPlugin(rootBrowserHolder, pluginsHarness),

packages/vitest/src/node/plugins/server.ts

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,66 @@ import type { ResolvedApiConfig, ResolvedConfig } from '../types/config'
44
import { defaultPort } from '../../constants'
55
import { resolveApiServerConfig } from '../config/resolveConfig'
66

7-
export function VitestConfigServer(harness: PluginHarness, globalConfig?: ResolvedConfig): Plugin {
8-
return {
9-
name: 'vitest:config:server',
10-
enforce: 'post',
11-
config: {
12-
order: 'post',
13-
handler(viteConfig) {
14-
// Custom user config, this plugin already received CLI overrides
15-
const testConfig = viteConfig.test ?? {}
16-
const isBrowserEnabled = !!testConfig.browser?.enabled
7+
export function VitestConfigServer(harness: PluginHarness, globalConfig?: ResolvedConfig): Plugin[] {
8+
return [
9+
{
10+
name: 'vitest:config:server-defaults',
11+
config: {
12+
// These static server toggles must be visible to other plugins that
13+
// read `server.hmr` in their own `config` hook — e.g.
14+
// `@vitejs/plugin-react` turns React Fast Refresh off only when it sees
15+
// HMR disabled. A `post` hook (below) runs after such plugins, so set
16+
// them here in a `pre` hook instead.
17+
order: 'pre',
18+
handler() {
19+
return {
20+
server: {
21+
preTransformRequests: false,
22+
hmr: false,
23+
open: false,
24+
},
25+
}
26+
},
27+
},
28+
},
29+
{
30+
name: 'vitest:config:server',
31+
enforce: 'post',
32+
config: {
33+
order: 'post',
34+
handler(viteConfig) {
35+
// Custom user config, this plugin already received CLI overrides
36+
const testConfig = viteConfig.test ?? {}
37+
const isBrowserEnabled = !!testConfig.browser?.enabled
1738

18-
const api = resolveApiServerConfig(
19-
testConfig,
20-
isBrowserEnabled ? harness._browserLastPort++ : defaultPort,
21-
harness.logger,
22-
) as ResolvedApiConfig
23-
testConfig.api = api
24-
if (globalConfig) {
25-
api.token = globalConfig.api.token
26-
api.tokenCreated = globalConfig.api.tokenCreated
27-
}
39+
const api = resolveApiServerConfig(
40+
testConfig,
41+
isBrowserEnabled ? harness._browserLastPort++ : defaultPort,
42+
harness.logger,
43+
) as ResolvedApiConfig
44+
testConfig.api = api
45+
if (globalConfig) {
46+
api.token = globalConfig.api.token
47+
api.tokenCreated = globalConfig.api.tokenCreated
48+
}
2849

29-
const server: ServerOptions = {
30-
...api,
31-
preTransformRequests: false,
32-
hmr: false,
33-
open: false,
34-
}
50+
const server: ServerOptions = {
51+
...api,
52+
}
3553

36-
// Always disable the websocket server in middlewareMode
37-
if (!isBrowserEnabled && api.middlewareMode) {
38-
server.ws = false
39-
}
40-
else if (viteConfig.server && 'ws' in viteConfig.server) {
41-
viteConfig.server.ws = undefined
42-
}
54+
// Always disable the websocket server in middlewareMode
55+
if (!isBrowserEnabled && api.middlewareMode) {
56+
server.ws = false
57+
}
58+
else if (viteConfig.server && 'ws' in viteConfig.server) {
59+
viteConfig.server.ws = undefined
60+
}
4361

44-
return {
45-
server,
46-
}
62+
return {
63+
server,
64+
}
65+
},
4766
},
4867
},
49-
}
68+
]
5069
}

packages/vitest/src/node/plugins/workspace.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export function WorkspaceVitestPlugin(
7676
config.server.watch = null
7777
},
7878
},
79-
VitestConfigServer(harness, globalConfig),
79+
...VitestConfigServer(harness, globalConfig),
8080
SsrRunnerFixerPlugin(harness),
8181
MetaEnvReplacerPlugin(),
8282
...CSSEnablerPlugin(),

test/e2e/test/override.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,29 @@ describe.each([
123123
})
124124
})
125125

126+
it('disables server.hmr before plugins read it in their own config hook', async () => {
127+
// `@vitejs/plugin-react` (and others) turn features like React Fast Refresh
128+
// off only when they observe `server.hmr === false` inside their `config`
129+
// hook. Vitest must disable HMR early enough for such plugins — which often
130+
// run as `enforce: 'post'` — to see it, otherwise Fast Refresh stays on and
131+
// injects `$RefreshSig$`, which is undefined outside a browser preamble.
132+
let observedHmr: unknown = 'unset'
133+
await resolveTestConfig({
134+
$viteConfig: {
135+
plugins: [
136+
{
137+
name: 'test:observe-hmr',
138+
enforce: 'post',
139+
config(userConfig) {
140+
observedHmr = userConfig.server?.hmr
141+
},
142+
},
143+
],
144+
},
145+
})
146+
expect(observedHmr).toBe(false)
147+
})
148+
126149
it('experimental fsModuleCache is inherited in a project', async () => {
127150
const v = await config({
128151
experimental: {

0 commit comments

Comments
 (0)