From a6cae595fa77c6f526fa4a7085b1a572412ce8a0 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Mon, 19 Oct 2026 11:24:18 +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. - In the client test posture the plugin added `browser` to every environment, so Node resolved `browser` exports too. - `msw/node` failed because `@mswjs/interceptors/ClientRequest` exports `null` for `browser`. - jsdom and happy-dom tests run in the client environment, and the Solid runtime is now inlined, so the ssr environment no longer gets `browser` in those runs. - The server posture, browser mode, and a user `external` entry for solid-js keep the old conditions. This builds on the runtime inlining from #433. Fixes #125 Co-Authored-By: Claude Opus 5.5 (1M context) --- .changeset/vitest-ssr-no-browser-condition.md | 5 ++ examples/start-ssr/test/run.mjs | 50 +++++++++++++++++++ src/index.ts | 28 ++++++++++- 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 .changeset/vitest-ssr-no-browser-condition.md diff --git a/.changeset/vitest-ssr-no-browser-condition.md b/.changeset/vitest-ssr-no-browser-condition.md new file mode 100644 index 0000000..4eb6a3b --- /dev/null +++ b/.changeset/vitest-ssr-no-browser-condition.md @@ -0,0 +1,5 @@ +--- +'@solidjs/vite-plugin': 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 (#125). diff --git a/examples/start-ssr/test/run.mjs b/examples/start-ssr/test/run.mjs index 73e659d..24b4a5b 100644 --- a/examples/start-ssr/test/run.mjs +++ b/examples/start-ssr/test/run.mjs @@ -6592,6 +6592,56 @@ async function runVitestMode() { : jestDomError || `empty root: ${JSON.stringify(emptySetupFiles)}, example root: ${JSON.stringify(exampleSetupFiles)}`, ); + + // Vitest passes the `ssr` environment's conditions to Node as + // `--conditions` for the packages it leaves external. A jsdom run used to + // put `browser` there, so Node-only packages failed to load: `msw/node` + // imports `@mswjs/interceptors/ClientRequest`, whose `browser` export is + // `null` (#125). jsdom: `client` keeps `browser` and `ssr` does not. + // node: neither gets it (server posture, unchanged). + const conditionsProbe = ` + import { resolveConfig } from 'vite'; + import solid from '@solidjs/vite-plugin'; + const out = {}; + for (const environment of ['jsdom', 'node']) { + const resolved = await resolveConfig( + { mode: 'test', configFile: false, logLevel: 'silent', plugins: [solid()], test: { environment } }, + 'serve', + ); + out[environment] = { + client: resolved.environments.client.resolve.conditions, + ssr: resolved.environments.ssr.resolve.conditions, + }; + } + console.log(JSON.stringify(out)); + `; + let testConditions; + let conditionsError = ''; + try { + testConditions = JSON.parse( + execFileSync(process.execPath, ['--input-type=module', '-e', conditionsProbe], { + cwd: exampleDir, + stdio: ['ignore', 'pipe', 'pipe'], + timeout: 60000, + }) + .toString() + .trim(), + ); + } catch (e) { + conditionsError = String(e.stderr || e); + } + const conditionsPass = + !conditionsError && + testConditions.jsdom.client.includes('browser') && + !testConditions.jsdom.ssr.includes('browser') && + !testConditions.node.ssr.includes('browser'); + record( + mode, + 'ssr-conditions', + 'jsdom tests keep the browser condition out of the ssr environment (Node --conditions)', + conditionsPass, + conditionsPass ? undefined : conditionsError || JSON.stringify(testConditions), + ); } // The CSP nonce on the request event (SSR_NONCE=1 wires src/middleware.ts, diff --git a/src/index.ts b/src/index.ts index 02db8ea..00c9bae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1216,6 +1216,10 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { let projectRoot = process.cwd(); let isTestMode = false; let serverTestPosture = false; + // True when a jsdom or happy-dom test run inlines the Solid runtime. The + // `ssr` environment then does not get the `browser` condition (see + // `configEnvironment`). + let domTestRuntimeInlined = false; // Lets the dependency scanner crawl `.tsrx` imports. Registered on every // environment's `optimizeDeps` (top-level for client, configEnvironment @@ -1428,6 +1432,7 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { observe = options.observe === true; projectRoot = userConfig.root || projectRoot; isTestMode = userConfig.mode === 'test'; + domTestRuntimeInlined = false; forceProduction = options.dev === false && command === 'serve' && !isTestMode; // Per-vitest-project posture: the client posture (browser conditions, // dom codegen, jsdom default) is right for DOM component tests but @@ -1626,6 +1631,14 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { }, }; } + const environment = userTest.environment ?? test.environment; + domTestRuntimeInlined = + !serverTestPosture && + !userTest.browser?.enabled && + (environment === 'jsdom' || environment === 'happy-dom') && + !userTest.server?.deps?.external?.find((item: string | RegExp) => + /solid-js/.test(item.toString()), + ); // jest-dom's DOM matchers have no place in a server-posture project; // vitest browser mode already has bundled jest-dom assertions // https://main.vitest.dev/guide/browser/assertion-api.html#assertion-api @@ -1749,7 +1762,20 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { // a server runtime (`test.environment: 'node'` / 'edge-runtime') // keep the default server conditions instead, so the framework's // real server build resolves (isServer true). - ...(isTestMode && !serverTestPosture && !opts.isSsrTargetWebworker ? ['browser'] : []), + // + // 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). jsdom and happy-dom tests run in the + // `client` environment with the runtime inlined, so `ssr` does not + // get `browser` in that case. + ...(isTestMode && + !serverTestPosture && + !opts.isSsrTargetWebworker && + !(domTestRuntimeInlined && name === 'ssr') + ? ['browser'] + : []), // Vite's `development|production` resolves to `development` in any // non-production mode, which would undo `dev: false` under `vite dev`. ...(forceProduction