Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/vitest-ssr-no-browser-condition.md
Original file line number Diff line number Diff line change
@@ -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).
50 changes: 50 additions & 0 deletions examples/start-ssr/test/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
28 changes: 27 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,10 @@ export default function solidPlugin(options: Partial<Options> = {}): 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
Expand Down Expand Up @@ -1428,6 +1432,7 @@ export default function solidPlugin(options: Partial<Options> = {}): 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
Expand Down Expand Up @@ -1626,6 +1631,14 @@ export default function solidPlugin(options: Partial<Options> = {}): 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
Expand Down Expand Up @@ -1749,7 +1762,20 @@ export default function solidPlugin(options: Partial<Options> = {}): 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
Expand Down
Loading