From 765a4069dda8dc4e0b5dc7a7921bb61a028793ee Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Mon, 19 Oct 2026 11:19:05 +0800 Subject: [PATCH] fix(vitest): keep the browser condition out of Node in DOM tests - Vitest passes the ssr environment's conditions to Node as `--conditions` for external packages. - The plugin added `browser` to every environment in test mode, so Node resolved `browser` exports too. - `msw/node` failed because `@mswjs/interceptors/ClientRequest` exports `null` for `browser`. - DOM tests run in the client environment, and the Solid runtime is now inlined, so the ssr environment no longer gets `browser` in jsdom and happy-dom runs. - Tests with `environment: 'node'`, browser mode, `ssr: true`, or a user `external` entry for solid-js keep the old conditions. This builds on the runtime inlining from #434. Fixes #125 Co-Authored-By: Claude Opus 5.5 (1M context) --- .changeset/vitest-ssr-no-browser-condition.md | 5 +++ examples/vite-6/package.json | 2 +- examples/vite-6/tests/node-conditions.mjs | 41 +++++++++++++++++++ src/index.ts | 24 ++++++++++- 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 .changeset/vitest-ssr-no-browser-condition.md create mode 100644 examples/vite-6/tests/node-conditions.mjs diff --git a/.changeset/vitest-ssr-no-browser-condition.md b/.changeset/vitest-ssr-no-browser-condition.md new file mode 100644 index 0000000..12af579 --- /dev/null +++ b/.changeset/vitest-ssr-no-browser-condition.md @@ -0,0 +1,5 @@ +--- +'vite-plugin-solid': patch +--- + +In jsdom and happy-dom tests, Node no longer gets the `browser` condition for packages that Vitest leaves external. Node-only packages such as `msw/node` now load under Vitest 4 and later (#125). diff --git a/examples/vite-6/package.json b/examples/vite-6/package.json index fd5c45e..3f5ebb4 100644 --- a/examples/vite-6/package.json +++ b/examples/vite-6/package.json @@ -8,7 +8,7 @@ "preview": "vite preview", "test": "vitest --browser.headless --run", "test-dev": "vitest", - "test:jsdom": "NODE_OPTIONS=--import=./tests/deno-conditions.mjs vitest --run --config vitest.jsdom.config.ts --pool=forks" + "test:jsdom": "NODE_OPTIONS=--import=./tests/deno-conditions.mjs vitest --run --config vitest.jsdom.config.ts --pool=forks && node tests/node-conditions.mjs" }, "devDependencies": { "@solidjs/testing-library": "^0.8.10", diff --git a/examples/vite-6/tests/node-conditions.mjs b/examples/vite-6/tests/node-conditions.mjs new file mode 100644 index 0000000..d55debc --- /dev/null +++ b/examples/vite-6/tests/node-conditions.mjs @@ -0,0 +1,41 @@ +// Vitest passes the `ssr` environment's conditions to Node as `--conditions` +// for the packages it leaves external. In a jsdom test run the plugin used to +// add `browser` there, so Node-only packages failed to load. For example, +// `msw/node` imports `@mswjs/interceptors/ClientRequest`, whose `browser` +// export is `null` (#125). +// +// Checks: +// - jsdom: `client` keeps `browser`, `ssr` does not. +// - node environment: `ssr` keeps `browser`, since its tests run there. +import { resolveConfig } from 'vite'; +import solid from 'vite-plugin-solid'; + +async function conditions(test) { + const resolved = await resolveConfig( + { mode: 'test', configFile: false, logLevel: 'silent', plugins: [solid()], test }, + 'serve', + ); + return { + client: resolved.environments.client.resolve.conditions, + ssr: resolved.environments.ssr.resolve.conditions, + }; +} + +const failures = []; +const jsdom = await conditions({ environment: 'jsdom' }); +if (!jsdom.client.includes('browser')) { + failures.push(`jsdom: client conditions lack browser: ${JSON.stringify(jsdom.client)}`); +} +if (jsdom.ssr.includes('browser')) { + failures.push(`jsdom: ssr conditions include browser: ${JSON.stringify(jsdom.ssr)}`); +} +const node = await conditions({ environment: 'node' }); +if (!node.ssr.includes('browser')) { + failures.push(`node: ssr conditions lack browser: ${JSON.stringify(node.ssr)}`); +} + +if (failures.length) { + for (const failure of failures) console.error(`FAIL: ${failure}`); + process.exit(1); +} +console.log('PASS: jsdom tests keep browser out of the ssr conditions'); diff --git a/src/index.ts b/src/index.ts index fd2ce0c..87b05c3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -218,6 +218,10 @@ export default function solidPlugin(options: Partial = {}): Plugin { // by the crawl in test mode. They are inlined with solid-js (see // `test.server.deps` below). const testRuntimeConsumers = new Set(); + // True when a DOM test run inlines the Solid runtime. The `ssr` + // environment then does not get the `browser` condition (see + // `configEnvironment` below). + let domTestRuntimeInlined = false; return { name: 'solid', @@ -228,6 +232,7 @@ export default function solidPlugin(options: Partial = {}): Plugin { replaceDev = options.dev === true || (options.dev !== false && command === 'serve'); projectRoot = userConfig.root; isTestMode = userConfig.mode === 'test'; + domTestRuntimeInlined = false; if (!userConfig.resolve) userConfig.resolve = {}; userConfig.resolve.alias = normalizeAliases(userConfig.resolve && userConfig.resolve.alias); @@ -310,6 +315,11 @@ export default function solidPlugin(options: Partial = {}): Plugin { }, }; } + const environment = userTest.environment ?? test.environment; + domTestRuntimeInlined = + !options.ssr && + !userTest.browser?.enabled && + (environment === 'jsdom' || environment === 'happy-dom'); } if (!userTest.browser?.enabled) { // vitest browser mode already has bundled jest-dom assertions @@ -367,10 +377,22 @@ export default function solidPlugin(options: Partial = {}): Plugin { config.resolve.conditions = [...defaultServerConditions]; } } + // Vitest passes the `ssr` environment's conditions to Node as + // `--conditions` for the packages it leaves external. With `browser` + // there, Node-only packages fail to load. For example, `msw/node` + // imports `@mswjs/interceptors/ClientRequest`, whose `browser` + // export is `null` (#125). DOM tests run in the `client` environment, + // and the Solid runtime is inlined in that case, so the `ssr` + // environment does not need `browser`. + const addBrowser = + isTestMode && + !opts.isSsrTargetWebworker && + !options.ssr && + !(domTestRuntimeInlined && name === 'ssr'); config.resolve.conditions = [ 'solid', ...(replaceDev ? ['development'] : []), - ...(isTestMode && !opts.isSsrTargetWebworker && !options.ssr ? ['browser'] : []), + ...(addBrowser ? ['browser'] : []), ...config.resolve.conditions, ];