diff --git a/packages/vitest/src/node/config/resolveConfig.ts b/packages/vitest/src/node/config/resolveConfig.ts index a52662784..540e8a03a 100644 --- a/packages/vitest/src/node/config/resolveConfig.ts +++ b/packages/vitest/src/node/config/resolveConfig.ts @@ -1029,7 +1029,7 @@ export async function resolveConfig( mode: options.mode || 'test', plugins: [ CliOverride(cliOptionsCopy), - VitestConfigServer(pluginsHarness), + ...VitestConfigServer(pluginsHarness), ...VitestConfig(pluginsHarness), ...VitestCorePlugin(pluginsHarness, options), ...BrowserLoaderPlugin(rootBrowserHolder, pluginsHarness), diff --git a/packages/vitest/src/node/plugins/server.ts b/packages/vitest/src/node/plugins/server.ts index 91166aad9..5ec5a5874 100644 --- a/packages/vitest/src/node/plugins/server.ts +++ b/packages/vitest/src/node/plugins/server.ts @@ -4,47 +4,66 @@ import type { ResolvedApiConfig, ResolvedConfig } from '../types/config' import { defaultPort } from '../../constants' import { resolveApiServerConfig } from '../config/resolveConfig' -export function VitestConfigServer(harness: PluginHarness, globalConfig?: ResolvedConfig): Plugin { - return { - name: 'vitest:config:server', - enforce: 'post', - config: { - order: 'post', - handler(viteConfig) { - // Custom user config, this plugin already received CLI overrides - const testConfig = viteConfig.test ?? {} - const isBrowserEnabled = !!testConfig.browser?.enabled +export function VitestConfigServer(harness: PluginHarness, globalConfig?: ResolvedConfig): Plugin[] { + return [ + { + name: 'vitest:config:server-defaults', + config: { + // These static server toggles must be visible to other plugins that + // read `server.hmr` in their own `config` hook — e.g. + // `@vitejs/plugin-react` turns React Fast Refresh off only when it sees + // HMR disabled. A `post` hook (below) runs after such plugins, so set + // them here in a `pre` hook instead. + order: 'pre', + handler() { + return { + server: { + preTransformRequests: false, + hmr: false, + open: false, + }, + } + }, + }, + }, + { + name: 'vitest:config:server', + enforce: 'post', + config: { + order: 'post', + handler(viteConfig) { + // Custom user config, this plugin already received CLI overrides + const testConfig = viteConfig.test ?? {} + const isBrowserEnabled = !!testConfig.browser?.enabled - const api = resolveApiServerConfig( - testConfig, - isBrowserEnabled ? harness._browserLastPort++ : defaultPort, - harness.logger, - ) as ResolvedApiConfig - testConfig.api = api - if (globalConfig) { - api.token = globalConfig.api.token - api.tokenCreated = globalConfig.api.tokenCreated - } + const api = resolveApiServerConfig( + testConfig, + isBrowserEnabled ? harness._browserLastPort++ : defaultPort, + harness.logger, + ) as ResolvedApiConfig + testConfig.api = api + if (globalConfig) { + api.token = globalConfig.api.token + api.tokenCreated = globalConfig.api.tokenCreated + } - const server: ServerOptions = { - ...api, - preTransformRequests: false, - hmr: false, - open: false, - } + const server: ServerOptions = { + ...api, + } - // Always disable the websocket server in middlewareMode - if (!isBrowserEnabled && api.middlewareMode) { - server.ws = false - } - else if (viteConfig.server && 'ws' in viteConfig.server) { - viteConfig.server.ws = undefined - } + // Always disable the websocket server in middlewareMode + if (!isBrowserEnabled && api.middlewareMode) { + server.ws = false + } + else if (viteConfig.server && 'ws' in viteConfig.server) { + viteConfig.server.ws = undefined + } - return { - server, - } + return { + server, + } + }, }, }, - } + ] } diff --git a/packages/vitest/src/node/plugins/workspace.ts b/packages/vitest/src/node/plugins/workspace.ts index 2e486499f..8490fbc13 100644 --- a/packages/vitest/src/node/plugins/workspace.ts +++ b/packages/vitest/src/node/plugins/workspace.ts @@ -76,7 +76,7 @@ export function WorkspaceVitestPlugin( config.server.watch = null }, }, - VitestConfigServer(harness, globalConfig), + ...VitestConfigServer(harness, globalConfig), SsrRunnerFixerPlugin(harness), MetaEnvReplacerPlugin(), ...CSSEnablerPlugin(), diff --git a/test/e2e/test/override.test.ts b/test/e2e/test/override.test.ts index 2e48df73c..7c7c17153 100644 --- a/test/e2e/test/override.test.ts +++ b/test/e2e/test/override.test.ts @@ -123,6 +123,29 @@ describe.each([ }) }) +it('disables server.hmr before plugins read it in their own config hook', async () => { + // `@vitejs/plugin-react` (and others) turn features like React Fast Refresh + // off only when they observe `server.hmr === false` inside their `config` + // hook. Vitest must disable HMR early enough for such plugins — which often + // run as `enforce: 'post'` — to see it, otherwise Fast Refresh stays on and + // injects `$RefreshSig$`, which is undefined outside a browser preamble. + let observedHmr: unknown = 'unset' + await resolveTestConfig({ + $viteConfig: { + plugins: [ + { + name: 'test:observe-hmr', + enforce: 'post', + config(userConfig) { + observedHmr = userConfig.server?.hmr + }, + }, + ], + }, + }) + expect(observedHmr).toBe(false) +}) + it('experimental fsModuleCache is inherited in a project', async () => { const v = await config({ experimental: {