[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix: disable server HMR before plugins read it in their config hook (#10731)

authored by

Vladimir and committed by
GitHub
(Jul 7, 2026, 3:55 PM +0200) e060b8fa 1c0ec344

+85 -43
+23
test/e2e/test/override.test.ts
··· 123 123 }) 124 124 }) 125 125 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 + 126 149 it('experimental fsModuleCache is inherited in a project', async () => { 127 150 const v = await config({ 128 151 experimental: {
+1 -1
packages/vitest/src/node/config/resolveConfig.ts
··· 1029 1029 mode: options.mode || 'test', 1030 1030 plugins: [ 1031 1031 CliOverride(cliOptionsCopy), 1032 - VitestConfigServer(pluginsHarness), 1032 + ...VitestConfigServer(pluginsHarness), 1033 1033 ...VitestConfig(pluginsHarness), 1034 1034 ...VitestCorePlugin(pluginsHarness, options), 1035 1035 ...BrowserLoaderPlugin(rootBrowserHolder, pluginsHarness),
+60 -41
packages/vitest/src/node/plugins/server.ts
··· 4 4 import { defaultPort } from '../../constants' 5 5 import { resolveApiServerConfig } from '../config/resolveConfig' 6 6 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 17 - 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 - } 28 - 29 - const server: ServerOptions = { 30 - ...api, 31 - preTransformRequests: false, 32 - hmr: false, 33 - open: false, 34 - } 35 - 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 - } 43 - 44 - return { 45 - server, 46 - } 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 + }, 47 27 }, 48 28 }, 49 - } 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 38 + 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 + } 49 + 50 + const server: ServerOptions = { 51 + ...api, 52 + } 53 + 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 + } 61 + 62 + return { 63 + server, 64 + } 65 + }, 66 + }, 67 + }, 68 + ] 50 69 }
+1 -1
packages/vitest/src/node/plugins/workspace.ts
··· 76 76 config.server.watch = null 77 77 }, 78 78 }, 79 - VitestConfigServer(harness, globalConfig), 79 + ...VitestConfigServer(harness, globalConfig), 80 80 SsrRunnerFixerPlugin(harness), 81 81 MetaEnvReplacerPlugin(), 82 82 ...CSSEnablerPlugin(),