From 37b56ede60a56418aaafe0fb4450a09a4ee7c1b0 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Mon, 19 Oct 2026 11:33:53 +0800 Subject: [PATCH 1/2] test: add vitest unit tests for the plugin - Add a root vitest config and a test:unit script, matching the next branch. - Run the unit tests in the vitest workflow. - Exclude test files from the declaration build. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/vitest.yml | 3 +++ package.json | 6 ++++-- pnpm-lock.yaml | 37 +++++++++++++++++++++++++++++++++++- tsconfig.json | 2 +- vitest.config.ts | 10 ++++++++++ 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 vitest.config.ts diff --git a/.github/workflows/vitest.yml b/.github/workflows/vitest.yml index fc062d92..aad075ac 100644 --- a/.github/workflows/vitest.yml +++ b/.github/workflows/vitest.yml @@ -27,6 +27,9 @@ jobs: - name: Install project dependencies run: pnpm i + - name: Run unit tests + run: pnpm run test:unit + - name: Install playwright browser working-directory: examples/vite-6 run: pnpm exec playwright install chromium diff --git a/package.json b/package.json index a2662f89..cba7d046 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "prepublishOnly": "pnpm build", "release": "pnpm build && changeset publish", "check": "package-check", - "test": "node scripts/test-examples.ts" + "test": "node scripts/test-examples.ts", + "test:unit": "vitest run" }, "repository": { "type": "git", @@ -68,7 +69,8 @@ "rollup-plugin-cleaner": "^1.0.0", "solid-js": "^1.9.3", "typescript": "^5.2.2", - "vite": "^6.0.0" + "vite": "^6.0.0", + "vitest": "^3.0.7" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.0.0 || ^7.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eed12c18..23823a84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,6 +90,9 @@ importers: vite: specifier: ^6.0.0 version: 6.0.9(@types/node@18.19.66)(lightningcss@1.32.0) + vitest: + specifier: ^3.0.7 + version: 3.0.7(@types/node@18.19.66)(@vitest/browser@3.0.7)(jsdom@26.0.0(supports-color@8.1.1))(lightningcss@1.32.0)(msw@2.7.3(@types/node@18.19.66)(typescript@5.7.2))(supports-color@8.1.1) examples/vite-3: dependencies: @@ -5968,6 +5971,28 @@ snapshots: '@types/node': 18.19.66 optional: true + '@vitest/browser@3.0.7(@types/node@18.19.66)(playwright@1.50.1)(typescript@5.7.2)(vite@6.0.9(@types/node@18.19.66)(lightningcss@1.32.0))(vitest@3.0.7)': + dependencies: + '@testing-library/dom': 10.4.0 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0) + '@vitest/mocker': 3.0.7(msw@2.7.3(@types/node@18.19.66)(typescript@5.7.2))(vite@6.0.9(@types/node@18.19.66)(lightningcss@1.32.0)) + '@vitest/utils': 3.0.7 + magic-string: 0.30.17 + msw: 2.7.3(@types/node@18.19.66)(typescript@5.7.2) + sirv: 3.0.1 + tinyrainbow: 2.0.0 + vitest: 3.0.7(@types/node@18.19.66)(@vitest/browser@3.0.7)(jsdom@26.0.0(supports-color@8.1.1))(lightningcss@1.32.0)(msw@2.7.3(@types/node@18.19.66)(typescript@5.7.2))(supports-color@8.1.1) + ws: 8.18.1 + optionalDependencies: + playwright: 1.50.1 + transitivePeerDependencies: + - '@types/node' + - bufferutil + - typescript + - utf-8-validate + - vite + optional: true + '@vitest/browser@3.0.7(@types/node@18.19.66)(playwright@1.50.1)(typescript@5.7.2)(vite@6.2.0(@types/node@18.19.66)(lightningcss@1.32.0))(vitest@3.0.7)': dependencies: '@testing-library/dom': 10.4.0 @@ -6038,6 +6063,16 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 + '@vitest/mocker@3.0.7(msw@2.7.3(@types/node@18.19.66)(typescript@5.7.2))(vite@6.0.9(@types/node@18.19.66)(lightningcss@1.32.0))': + dependencies: + '@vitest/spy': 3.0.7 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + msw: 2.7.3(@types/node@18.19.66)(typescript@5.7.2) + vite: 6.0.9(@types/node@18.19.66)(lightningcss@1.32.0) + optional: true + '@vitest/mocker@3.0.7(msw@2.7.3(@types/node@18.19.66)(typescript@5.7.2))(vite@6.2.0(@types/node@18.19.66)(lightningcss@1.32.0))': dependencies: '@vitest/spy': 3.0.7 @@ -8040,7 +8075,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 18.19.66 - '@vitest/browser': 3.0.7(@types/node@18.19.66)(playwright@1.50.1)(typescript@5.7.2)(vite@6.2.0(@types/node@18.19.66)(lightningcss@1.32.0))(vitest@3.0.7) + '@vitest/browser': 3.0.7(@types/node@18.19.66)(playwright@1.50.1)(typescript@5.7.2)(vite@6.0.9(@types/node@18.19.66)(lightningcss@1.32.0))(vitest@3.0.7) jsdom: 26.0.0(supports-color@8.1.1) transitivePeerDependencies: - jiti diff --git a/tsconfig.json b/tsconfig.json index 70191cbf..9c12304d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "src", "cypress" ], "exclude": [ - "**/*.spec.ts" + "**/*.spec.ts", "**/*.test.ts" ], "compilerOptions": { "target": "ESNext", diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..2f1a903a --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +// The plugin's own unit tests (`pnpm test:unit`). The example apps under +// examples/* carry their own vitest configs and run through `pnpm test`. +export default defineConfig({ + test: { + include: ['src/**/*.test.ts'], + environment: 'node', + }, +}); From 9c17655791a5f3e815f2f3c9d62953a801fe8483 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Mon, 19 Oct 2026 11:37:51 +0800 Subject: [PATCH 2/2] fix: resolve solid-js server build for webworker SSR targets - With ssr.target set to webworker, the SSR build resolved the browser build of solid-js and renderToString threw. - Vite 6+: add the worker export condition to the ssr environment. - Vite 4 and 5: set ssr.resolve.conditions with the worker condition, keeping the top-level conditions it replaces. - 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 | 38 +++++++--- src/ssr-webworker.test.ts | 73 ++++++++++++++++++++ 3 files changed, 108 insertions(+), 8 deletions(-) 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 00000000..cef1d46f --- /dev/null +++ b/.changeset/ssr-webworker-worker-condition.md @@ -0,0 +1,5 @@ +--- +'vite-plugin-solid': patch +--- + +Server builds with `ssr.target: 'webworker'` now resolve the server build of `solid-js`. The plugin adds the `worker` export condition to that build, so `renderToString` and the other server APIs no longer resolve to their browser stubs (#153). This works on Vite 4 and later. diff --git a/src/index.ts b/src/index.ts index 82770246..87338e67 100644 --- a/src/index.ts +++ b/src/index.ts @@ -272,6 +272,30 @@ export default function solidPlugin(options: Partial = {}): Plugin { } } + // Before Vite 6, conditions are set here for every build. + const legacyConditions = [ + 'solid', + ...(replaceDev ? ['development'] : []), + ...(userConfig.mode === 'test' && !options.ssr ? ['browser'] : []), + ]; + // A server build with `ssr.target: 'webworker'` resolves solid-js to its + // browser build, so add the `worker` condition, which selects the server + // build. #153 Vite 4 and 5 read `ssr.resolve.conditions` instead of + // `resolve.conditions` when it is set, so it repeats the top-level list + // unless the user set their own. Vite 3 has no such option. + const userSsrConditions = (userConfig.ssr as any)?.resolve?.conditions; + const legacySsr = + userConfig.ssr?.target === 'webworker' + ? { + ...solidPkgsConfig.ssr, + resolve: { + conditions: userSsrConditions + ? ['solid', 'worker'] + : [...legacyConditions, ...(userConfig.resolve?.conditions ?? []), 'worker'], + }, + } + : solidPkgsConfig.ssr; + return { /** * We only need esbuild on .ts or .js files. @@ -279,13 +303,7 @@ export default function solidPlugin(options: Partial = {}): Plugin { */ // esbuild: { include: /\.ts$/ }, resolve: { - conditions: isVite6OrNewer - ? undefined - : [ - 'solid', - ...(replaceDev ? ['development'] : []), - ...(userConfig.mode === 'test' && !options.ssr ? ['browser'] : []), - ], + conditions: isVite6OrNewer ? undefined : legacyConditions, dedupe: nestedDeps, alias: [{ find: /^solid-refresh$/, replacement: runtimePublicPath }], }, @@ -300,7 +318,7 @@ export default function solidPlugin(options: Partial = {}): Plugin { ? { rolldownOptions: { transform: { jsx: 'preserve' as const } } } : {}), }, - ...(!isVite6OrNewer ? { ssr: solidPkgsConfig.ssr } : {}), + ...(!isVite6OrNewer ? { ssr: legacySsr } : {}), ...(test.server ? { test } : {}), }; }, @@ -320,6 +338,10 @@ 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 build of solid-js. Its + // `worker` export points at the server build. #153 + ...(opts.isSsrTargetWebworker ? ['worker'] : []), ...(replaceDev ? ['development'] : []), ...(isTestMode && !opts.isSsrTargetWebworker && !options.ssr ? ['browser'] : []), ...config.resolve.conditions, diff --git a/src/ssr-webworker.test.ts b/src/ssr-webworker.test.ts new file mode 100644 index 00000000..b9fc4f5e --- /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 Rollup } 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: '1.9.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 Rollup.RollupOutput | Rollup.RollupOutput[]; + const output = Array.isArray(result) ? result[0] : result; + return output.output + .filter((chunk): chunk is Rollup.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"'); + }); +});