Skip to content
Open
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/ssr-webworker-worker-condition.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions .github/workflows/vitest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
37 changes: 36 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 30 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,20 +272,38 @@ export default function solidPlugin(options: Partial<Options> = {}): 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.
* .tsx & .jsx files are handled by us
*/
// 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 }],
},
Expand All @@ -300,7 +318,7 @@ export default function solidPlugin(options: Partial<Options> = {}): Plugin {
? { rolldownOptions: { transform: { jsx: 'preserve' as const } } }
: {}),
},
...(!isVite6OrNewer ? { ssr: solidPkgsConfig.ssr } : {}),
...(!isVite6OrNewer ? { ssr: legacySsr } : {}),
...(test.server ? { test } : {}),
};
},
Expand All @@ -320,6 +338,10 @@ export default function solidPlugin(options: Partial<Options> = {}): 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,
Expand Down
73 changes: 73 additions & 0 deletions src/ssr-webworker.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>) {
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"');
});
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"src", "cypress"
],
"exclude": [
"**/*.spec.ts"
"**/*.spec.ts", "**/*.test.ts"
],
"compilerOptions": {
"target": "ESNext",
Expand Down
10 changes: 10 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -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',
},
});
Loading