From c10fe96a6a1cc3e56e4f10c6f3fb4816af17c412 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Mon, 19 Oct 2026 11:29:19 +0800 Subject: [PATCH] fix: resolve solid server builds for webworker SSR targets - With ssr.target set to webworker, Vite gives the ssr environment the client conditions, so solid-js and @solidjs/web resolved to their browser builds and renderToString threw. - Add the worker export condition to that environment. Solid maps worker to its server build. - Add build tests for the webworker and node targets. Fixes #153 Co-Authored-By: Claude Opus 5.5 (1M context) --- .changeset/ssr-webworker-worker-condition.md | 5 ++ src/index.ts | 5 ++ src/ssr-webworker.test.ts | 73 ++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 .changeset/ssr-webworker-worker-condition.md create mode 100644 src/ssr-webworker.test.ts diff --git a/.changeset/ssr-webworker-worker-condition.md b/.changeset/ssr-webworker-worker-condition.md new file mode 100644 index 0000000..b517f2a --- /dev/null +++ b/.changeset/ssr-webworker-worker-condition.md @@ -0,0 +1,5 @@ +--- +'@solidjs/vite-plugin': patch +--- + +Server builds with `ssr.target: 'webworker'` now resolve the server builds of `solid-js` and `@solidjs/web`. The plugin adds the `worker` export condition to that environment, so `renderToString` and the other server APIs no longer resolve to their browser stubs (#153). diff --git a/src/index.ts b/src/index.ts index 7b9ab11..7913611 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1699,6 +1699,11 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { } config.resolve.conditions = [ 'solid', + // A server build with `ssr.target: 'webworker'` starts from Vite's + // client conditions, which select the browser builds of `solid-js` + // and `@solidjs/web`. Their `worker` export points at the server + // build, so add it for that environment. #153 + ...(opts.isSsrTargetWebworker ? ['worker'] : []), ...(replaceDev ? ['development'] : []), // `development` nests above `observe` in the runtime's exports, so // both may be present: dev serve keeps the dev build, builds get the diff --git a/src/ssr-webworker.test.ts b/src/ssr-webworker.test.ts new file mode 100644 index 0000000..af743c5 --- /dev/null +++ b/src/ssr-webworker.test.ts @@ -0,0 +1,73 @@ +import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { build, type Rolldown } from 'vite'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import solidPlugin from './index.js'; + +let root: string; + +beforeAll(async () => { + root = await mkdtemp(path.join(tmpdir(), 'solid-ssr-worker-')); + // Mirrors the export map of solid-js: `worker` selects the server build. + const solid = path.join(root, 'node_modules/solid-js'); + await mkdir(solid, { recursive: true }); + await writeFile( + path.join(solid, 'package.json'), + JSON.stringify({ + name: 'solid-js', + version: '2.0.0', + type: 'module', + exports: { + '.': { + worker: './server.js', + browser: './browser.js', + node: './server.js', + default: './browser.js', + }, + }, + }), + ); + await writeFile(path.join(solid, 'server.js'), 'export const build = "server";\n'); + await writeFile(path.join(solid, 'browser.js'), 'export const build = "browser";\n'); + await writeFile(path.join(root, 'package.json'), JSON.stringify({ name: 'app', type: 'module' })); + await writeFile(path.join(root, 'entry.js'), "export { build } from 'solid-js';\n"); +}); + +afterAll(async () => { + await rm(root, { recursive: true, force: true }); +}); + +async function buildSsr(ssr: Record) { + const result = (await build({ + root, + configFile: false, + logLevel: 'silent', + plugins: [solidPlugin({ ssr: true })], + ssr: { noExternal: true, ...ssr }, + build: { + ssr: path.join(root, 'entry.js'), + write: false, + minify: false, + }, + })) as Rolldown.RolldownOutput | Rolldown.RolldownOutput[]; + const output = Array.isArray(result) ? result[0] : result; + return output.output + .filter((chunk): chunk is Rolldown.OutputChunk => chunk.type === 'chunk') + .map((chunk) => chunk.code) + .join('\n'); +} + +describe('ssr.target webworker', () => { + // #153: the webworker SSR build resolved the browser build of solid-js. + it('resolves the server build through the worker condition', async () => { + const code = await buildSsr({ target: 'webworker' }); + expect(code).toContain('"server"'); + expect(code).not.toContain('"browser"'); + }); + + it('still resolves the server build for the node target', async () => { + const code = await buildSsr({}); + expect(code).toContain('"server"'); + }); +});