From bfca1a182475ae98674551f89e1b6aa12def9830 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Sat, 5 Sep 2026 23:19:34 -0700 Subject: [PATCH 1/3] Drop the async render head-commit workaround; require solid 2.0.0-rc.7 rc.7 (solidjs/solid#3292) freezes the response head when an awaited renderToStream completes, before the render is disposed, so the generated SSR entry no longer needs to commit the stub from onCompleteAll and authored entries need no hook. Peer floors move to ^2.0.0-rc.7; README caveat and the unreleased render-mode changeset drop the workaround wording. Against rc.6 the render-mode suite fails exactly the six async head assertions (status/header/Location, dev+prod); the rc.7 ride commit (catalog + lockfile + minimumReleaseAgeExclude) turns them green. Co-authored-by: Cursor --- .changeset/start-render-mode.md | 2 +- README.md | 13 ++++--------- package.json | 4 ++-- src/ssr/index.ts | 17 ++--------------- 4 files changed, 9 insertions(+), 27 deletions(-) diff --git a/.changeset/start-render-mode.md b/.changeset/start-render-mode.md index b4586c4..91f864e 100644 --- a/.changeset/start-render-mode.md +++ b/.changeset/start-render-mode.md @@ -2,4 +2,4 @@ '@solidjs/vite-plugin': minor --- -`start.renderMode: 'stream' | 'async'` (default `'stream'`), plus a per-request form and a runtime override — the fix for streaming SSR leaving `` fallbacks unresolved for clients that never run JavaScript (solidjs/solid#3280). `'async'` makes the generated handler adopt the `renderToStream` result's thenable, which resolves with the complete HTML once every boundary has settled: nothing has flushed, so each boundary's content is spliced in place of its placeholder — no fallback markup, no swap templates or scripts — while hydration data still serializes and JavaScript clients hydrate as before. The string then takes `createSSRResponse`'s string path: the response head commits, the document gets the doctype and client-entry injection, and a `Location` written mid-render becomes a real 3xx instead of the post-flush script redirect. The tradeoffs are inherent and documented: time-to-first-byte waits for the slowest boundary and the whole page buffers in memory; `deferStream` is moot (everything defers). The per-request form follows the `middleware`/`setup` convention — `renderMode: './src/render-mode.ts'`, a module default-exporting `(event) => 'stream' | 'async' | Promise<...>` run inside the request scope after the middleware chain — for policies like "complete documents for crawler user agents or `?nojs`, streaming for everyone else". Hosts driving the handler directly pass `handleRequest(request, { renderMode })`; precedence is that runtime option, then the module function, then the static config, and an invalid value from any source is rejected with an actionable error (unknown literals and missing module paths fail at config time). Works identically for authored entries. Generated entries also commit the response head at render completion (`onCompleteAll`) so `httpStatus`/`httpHeader` declarations survive the runtime's dispose-before-resolve in the awaited path; stream mode is unchanged. +`start.renderMode: 'stream' | 'async'` (default `'stream'`), plus a per-request form and a runtime override — the fix for streaming SSR leaving `` fallbacks unresolved for clients that never run JavaScript (solidjs/solid#3280). `'async'` makes the generated handler adopt the `renderToStream` result's thenable, which resolves with the complete HTML once every boundary has settled: nothing has flushed, so each boundary's content is spliced in place of its placeholder — no fallback markup, no swap templates or scripts — while hydration data still serializes and JavaScript clients hydrate as before. The string then takes `createSSRResponse`'s string path: the response head commits, the document gets the doctype and client-entry injection, and a `Location` written mid-render becomes a real 3xx instead of the post-flush script redirect. The tradeoffs are inherent and documented: time-to-first-byte waits for the slowest boundary and the whole page buffers in memory; `deferStream` is moot (everything defers). The per-request form follows the `middleware`/`setup` convention — `renderMode: './src/render-mode.ts'`, a module default-exporting `(event) => 'stream' | 'async' | Promise<...>` run inside the request scope after the middleware chain — for policies like "complete documents for crawler user agents or `?nojs`, streaming for everyone else". Hosts driving the handler directly pass `handleRequest(request, { renderMode })`; precedence is that runtime option, then the module function, then the static config, and an invalid value from any source is rejected with an actionable error (unknown literals and missing module paths fail at config time). Works identically for authored entries; stream mode is unchanged. Requires `solid-js` / `@solidjs/web` `^2.0.0-rc.7`, which freezes the response head when the awaited render completes so `httpStatus` / `httpHeader` declarations reach the response (solidjs/solid#3292). diff --git a/README.md b/README.md index c523f1b..30734d5 100644 --- a/README.md +++ b/README.md @@ -435,15 +435,10 @@ runtime option, then the module function's result, then the static config; an unknown value from any of the three is an error naming its source. The mode applies to generated and authored entries alike — an authored `render()` returning a `renderToStream` result is awaited the same way (and -in production its client-entry reference is still rewritten). One caveat for -authored entries: `httpStatus()` / `httpHeader()` declarations made during -the render are reverted when the runtime disposes it, which under `'async'` -happens before the response head is committed — the generated entry commits -the head at render completion (`renderToStream`'s `onCompleteAll`) to keep -them, so an authored entry that needs them under `'async'` should pass the -same hook (`onCompleteAll: () => commitResponseStub(getRequestEvent().response)`); -a `Location` written straight onto `event.response.headers` is unaffected. -Server mode only — in client mode the served shell has no boundaries to +in production its client-entry reference is still rewritten). `httpStatus()` / +`httpHeader()` declarations survive either mode: the runtime freezes the +response head when the awaited render completes (`@solidjs/web` 2.0.0-rc.7+), +just as streaming freezes it at shell flush. Server mode only — in client mode the served shell has no boundaries to settle, so the option is a documented no-op there. **`env`** — first-party typed environment variables. A schema file at the diff --git a/package.json b/package.json index 464d6cd..322511e 100644 --- a/package.json +++ b/package.json @@ -91,9 +91,9 @@ }, "peerDependencies": { "@solidjs/start-devtools": "^1.0.0-next.2", - "@solidjs/web": "^2.0.0-rc.0", + "@solidjs/web": "^2.0.0-rc.7", "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", - "solid-js": "^2.0.0-rc.0", + "solid-js": "^2.0.0-rc.7", "vite": "^8.0.0 || ^9.0.0" }, "peerDependenciesMeta": { diff --git a/src/ssr/index.ts b/src/ssr/index.ts index daf7480..90f0005 100644 --- a/src/ssr/index.ts +++ b/src/ssr/index.ts @@ -762,9 +762,9 @@ export function startServe( ].join('\n'); } const { app } = requireEntries(); - const streamOptions = `{ manifest, onCompleteAll: commitResponseHead${serverComponents ? ', plugins: [ServerComponentPlugin]' : ''} }`; + const streamOptions = `{ manifest${serverComponents ? ', plugins: [ServerComponentPlugin]' : ''} }`; return [ - `import { renderToStream, getRequestEvent, commitResponseStub } from '@solidjs/web';`, + `import { renderToStream${setupPath ? ', getRequestEvent' : ''} } from '@solidjs/web';`, ...(serverComponents ? [ `import { configureServerFunctionsServer } from '@solidjs/web/server-functions';`, @@ -797,19 +797,6 @@ export function startServe( ``, ] : []), - // Commits the response head when the render completes — BEFORE the - // runtime disposes the render. `httpStatus`/`httpHeader` register - // cleanups that revert their declarations unless the stub is already - // committed; streaming commits at shell flush (this is a no-op there), - // but the async render mode resolves the complete document only after - // that disposal (`renderToStream(...).then` disposes, then resolves), - // so without this the settled 404 / Location would be reverted before - // createSSRResponse's string path could commit them. - `function commitResponseHead() {`, - ` const event = getRequestEvent();`, - ` if (event) commitResponseStub(event.response);`, - `}`, - ``, ...(setupPath ? [ // The per-request seam: the hook sees the same event the From ec7ba9bf4b5a3c65c4a18937e51da69985618a92 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Tue, 8 Sep 2026 10:00:19 -0700 Subject: [PATCH 2/3] test: ride solid 2.0.0-rc.7 rc.7 ships solidjs/solid#3292: an awaited renderToStream freezes the response head before dispose, which is the runtime behaviour the previous commit's peer floor (^2.0.0-rc.7) requires. The workspace catalog moves to ^2.0.0-rc.7 (minimumReleaseAgeExclude extended per the existing pattern) and the lockfile re-resolves solid-js, @solidjs/web, @solidjs/signals, @solidjs/compiler (+ platform binaries), @solidjs/babel-plugin and @solidjs/diagnostics to rc.7. The plugin's own caret ranges already float and are left as-is. Audited the rc.7 notes against the plugin: no hard-coded dist/dev.* paths (bd22ac8 rename), no patch-channel option or test (d601119). Co-authored-by: Cursor --- pnpm-lock.yaml | 152 ++++++++++++++++++++++---------------------- pnpm-workspace.yaml | 28 ++++---- 2 files changed, 90 insertions(+), 90 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9de8af6..cee57be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,11 +7,11 @@ settings: catalogs: default: '@solidjs/web': - specifier: ^2.0.0-rc.6 - version: 2.0.0-rc.6 + specifier: ^2.0.0-rc.7 + version: 2.0.0-rc.7 solid-js: - specifier: ^2.0.0-rc.6 - version: 2.0.0-rc.6 + specifier: ^2.0.0-rc.7 + version: 2.0.0-rc.7 importers: @@ -25,13 +25,13 @@ importers: version: 7.29.7 '@solidjs/babel-plugin': specifier: ^2.0.0-rc.6 - version: 2.0.0-rc.6(@babel/core@7.29.7)(@tsrx/core@0.1.63) + version: 2.0.0-rc.7(@babel/core@7.29.7)(@tsrx/core@0.1.63) '@solidjs/compiler': specifier: ^2.0.0-rc.6 - version: 2.0.0-rc.6 + version: 2.0.0-rc.7 '@solidjs/web': - specifier: ^2.0.0-rc.0 - version: 2.0.0-rc.6(solid-js@2.0.0-rc.6) + specifier: ^2.0.0-rc.7 + version: 2.0.0-rc.7(solid-js@2.0.0-rc.7) '@testing-library/jest-dom': specifier: ^5.16.6 || ^5.17.0 || ^6.* version: 6.10.0(@testing-library/dom@10.4.1) @@ -68,10 +68,10 @@ importers: version: 0.2.2 '@solidjs/diagnostics': specifier: ^2.0.0-rc.3 - version: 2.0.0-rc.6(vitest@4.1.11) + version: 2.0.0-rc.7(vitest@4.1.11) '@solidjs/start-devtools': specifier: ^1.0.0-next.3 - version: 1.0.0-next.4(@solidjs/web@2.0.0-rc.6(solid-js@2.0.0-rc.6))(solid-js@2.0.0-rc.6) + version: 1.0.0-next.4(@solidjs/web@2.0.0-rc.7(solid-js@2.0.0-rc.7))(solid-js@2.0.0-rc.7) '@types/node': specifier: ^24.0.0 version: 24.13.3 @@ -98,7 +98,7 @@ importers: version: 1.0.0(rollup@4.62.2) solid-js: specifier: ^2.0.0-rc.0 - version: 2.0.0-rc.6 + version: 2.0.0-rc.7 typescript: specifier: ^5.2.2 version: 5.9.3 @@ -110,10 +110,10 @@ importers: dependencies: '@solidjs/web': specifier: 'catalog:' - version: 2.0.0-rc.6(solid-js@2.0.0-rc.6) + version: 2.0.0-rc.7(solid-js@2.0.0-rc.7) solid-js: specifier: 'catalog:' - version: 2.0.0-rc.6 + version: 2.0.0-rc.7 devDependencies: '@solidjs/vite-plugin': specifier: workspace:* @@ -126,10 +126,10 @@ importers: dependencies: '@solidjs/web': specifier: 'catalog:' - version: 2.0.0-rc.6(solid-js@2.0.0-rc.6) + version: 2.0.0-rc.7(solid-js@2.0.0-rc.7) solid-js: specifier: 'catalog:' - version: 2.0.0-rc.6 + version: 2.0.0-rc.7 devDependencies: '@solidjs/vite-plugin': specifier: workspace:* @@ -142,10 +142,10 @@ importers: dependencies: '@solidjs/web': specifier: 'catalog:' - version: 2.0.0-rc.6(solid-js@2.0.0-rc.6) + version: 2.0.0-rc.7(solid-js@2.0.0-rc.7) solid-js: specifier: 'catalog:' - version: 2.0.0-rc.6 + version: 2.0.0-rc.7 devDependencies: '@solidjs/vite-plugin': specifier: workspace:* @@ -158,10 +158,10 @@ importers: dependencies: '@solidjs/web': specifier: 'catalog:' - version: 2.0.0-rc.6(solid-js@2.0.0-rc.6) + version: 2.0.0-rc.7(solid-js@2.0.0-rc.7) solid-js: specifier: 'catalog:' - version: 2.0.0-rc.6 + version: 2.0.0-rc.7 valibot: specifier: ^1.1.0 version: 1.4.2(typescript@5.9.3) @@ -183,10 +183,10 @@ importers: dependencies: '@solidjs/web': specifier: 'catalog:' - version: 2.0.0-rc.6(solid-js@2.0.0-rc.6) + version: 2.0.0-rc.7(solid-js@2.0.0-rc.7) solid-js: specifier: 'catalog:' - version: 2.0.0-rc.6 + version: 2.0.0-rc.7 devDependencies: '@solidjs/vite-plugin': specifier: workspace:* @@ -205,14 +205,14 @@ importers: dependencies: '@solidjs/web': specifier: 'catalog:' - version: 2.0.0-rc.6(solid-js@2.0.0-rc.6) + version: 2.0.0-rc.7(solid-js@2.0.0-rc.7) solid-js: specifier: 'catalog:' - version: 2.0.0-rc.6 + version: 2.0.0-rc.7 devDependencies: '@solidjs/testing-library': specifier: ^1.0.0-beta.2 - version: 1.0.0-beta.2(@solidjs/web@2.0.0-rc.6(solid-js@2.0.0-rc.6))(solid-js@2.0.0-rc.6) + version: 1.0.0-beta.2(@solidjs/web@2.0.0-rc.7(solid-js@2.0.0-rc.7))(solid-js@2.0.0-rc.7) '@solidjs/vite-plugin': specifier: workspace:* version: link:../.. @@ -1362,8 +1362,8 @@ packages: resolution: {integrity: sha512-T4Wyi9lUuz0a1C2OHuzqZ0aFOCI0AmaGTb2LP9sHgWdoHXlB3JU02gfBpa0Y081G/gFsJYpQ/R0iCJRzF/nknw==} hasBin: true - '@solidjs/babel-plugin@2.0.0-rc.6': - resolution: {integrity: sha512-bPw4UjS4OGgIHxetWwEj1OEBpU59RYt7t2jE1QPxqYlomJiS2fQFLiXFy62rkqme+zAFVWNV4SMyS+2THQI2Mw==} + '@solidjs/babel-plugin@2.0.0-rc.7': + resolution: {integrity: sha512-cKcyVbOh8WC7ywV3txxfNrFRtTa7t8O3tXDXRf3RE3DKyZr+hZmbEOq6q/T6smaJXS3ekzuUDVlQrG1+HErnhg==} peerDependencies: '@babel/core': ^7.20.12 '@tsrx/core': 0.1.63 @@ -1371,48 +1371,48 @@ packages: '@tsrx/core': optional: true - '@solidjs/compiler-darwin-arm64@2.0.0-rc.6': - resolution: {integrity: sha512-uTZeFIIqsoDEgp4YUDrmnrIt63Hgz5quEIDexTnzcL6sPUpSMFoDGtNR5db6+d/pCUXAAE8ZgimpG0EJ6gOOiw==} + '@solidjs/compiler-darwin-arm64@2.0.0-rc.7': + resolution: {integrity: sha512-xJ7FoPrFV94LMuEPNJ2nIGFlqhVGR+s5GAr0nzMRMdWAimriF0sZ8clxlIqPS3v2oaNE9JW7EDCdJysD3JezWg==} cpu: [arm64] os: [darwin] - '@solidjs/compiler-darwin-x64@2.0.0-rc.6': - resolution: {integrity: sha512-GmUujhjlOT9TBm10+8qS8Ft0sKc5that4CFmj4klbt40PywwPDfdhEzlw+awhlFgl+7FN75q+tRQdQJFNcrAgA==} + '@solidjs/compiler-darwin-x64@2.0.0-rc.7': + resolution: {integrity: sha512-rcu8wcxeO0QWXu69yFaSf3ZR1KlsPDCzmRmdA8fX/cte96Ox0r6GfMXE+5CH/gg9dSaXY3qwwmpSG9AEvLxZQg==} cpu: [x64] os: [darwin] - '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.6': - resolution: {integrity: sha512-/edSzc/fjl+QCYeaAxSEQwpOBiaB5DNrb8EUm4zKQHGWY1WwFeKSkSLoMKwnhmwCu+SIt1Z8fTEG6lZirUj1hw==} + '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.7': + resolution: {integrity: sha512-EhiLKgLcFkHWYOx3Pds3px0onhTbzpDfenhsuDy1R7ViZYALXXBlZ+EUSE85rKwyCM0sgcR/kVMswL2V8sJXng==} cpu: [arm64] os: [linux] - '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.6': - resolution: {integrity: sha512-EWb0ypXtL7LbLn70M7O6FmK3pOKNDX9HaQETCiwZxjj/AXP7zYDsb7H5M1HsprbeXspij5+gft9Oeq7NfJJ+mQ==} + '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.7': + resolution: {integrity: sha512-ymN3hIqzH3msWt3lcU3vqILnzRlB1rNbv1BlUqXkwVZByjFw/bJQ+BgncIq0WqNH6ciQ5nhZ1E7ECgGw/SNwEA==} cpu: [x64] os: [linux] - '@solidjs/compiler-wasm32-wasi@2.0.0-rc.6': - resolution: {integrity: sha512-aQCYH+d3TAx1i05vtdINSRnKxPFL8VnkSOoab2OkX3rmxwCGxpRRUvj+fOePiF28XO4ijDoY7Bty4QsP1jbUUw==} + '@solidjs/compiler-wasm32-wasi@2.0.0-rc.7': + resolution: {integrity: sha512-k4vN+EHRtoIxj0D8d2VYHPEnWIok6kokbZskpn1pXrOoXBkj0OmqzzyJVBvSRYx6cWDWEjCKSWGuufRRZtgmjA==} engines: {node: '>=14.0.0'} - '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.6': - resolution: {integrity: sha512-8luAnqBxHJxEfw8SKqKGV1W2x+j1yk0G2rb242/z45pbVkQz+J5HNKMYSnvfKN3B2VpIvhZ447yph449wDKpEA==} + '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.7': + resolution: {integrity: sha512-pZtrkWmiiZ/NRwyMASAitwAa4EO2jlt4z8Z5K9NoljnBreWrCsz4HmIKSIeZ+17++WwAyeZo2byon0AEv6FzRQ==} cpu: [x64] os: [win32] - '@solidjs/compiler@2.0.0-rc.6': - resolution: {integrity: sha512-3B6zdACJp+lImXJW/E0E5cXfsMlMXSIg9+s8me2sxz5Q4juGptiW+aUtluDb2e+lCUZt7kDVZdy5eHOmZ3T/mA==} + '@solidjs/compiler@2.0.0-rc.7': + resolution: {integrity: sha512-jhFq/QcoUM34760NuQiSvMDInZ+3AEHwA354VELRDpAJwuiMpPbEvkSH6qqfgneDzVwCiCtLbjScqUEymU7R1A==} - '@solidjs/diagnostics@2.0.0-rc.6': - resolution: {integrity: sha512-Q+4x6iwJCW+mV2jVMboaKubdjEUruirbgp+I4sa0XfNZFrbL1EQluiNqVpYL4Q0HszTzboRh2a2Rzob7OG7DTw==} + '@solidjs/diagnostics@2.0.0-rc.7': + resolution: {integrity: sha512-XppGGFRGc7dtX8i+eKn04A0+q2AGLdbTUbHqRhSRaN81aGHOAhMEeyXWzSoY9tABpPgy4jga1m9t4xz9FwAJzA==} peerDependencies: vitest: '>=2.0.0' peerDependenciesMeta: vitest: optional: true - '@solidjs/signals@2.0.0-rc.6': - resolution: {integrity: sha512-lPqwZNLPq1Z9CBvgXkMvi1ZFr5OHUiFNz1X40+yehszDWEbJkneZx7BGKIe9eMT/AN1NSL+PMjOiMyZaqVB2xw==} + '@solidjs/signals@2.0.0-rc.7': + resolution: {integrity: sha512-JY0OJ5nGeqxGKOAqCtuoHkFBkaWQYxmf+yBQE8vw/o9C7yUF+Kan9PwcrL0ZHR6+MgyixBUtTfBigwou5hwW5w==} '@solidjs/start-devtools@1.0.0-next.4': resolution: {integrity: sha512-RNjndz1PfUicxJi3XHwjoIsu9AHeOhpP8Y/bgRmumR5Z9ymdMsiRCL89Fq1YqxhBnHf+GL4kX/VvC+F8lgmTVA==} @@ -1427,10 +1427,10 @@ packages: '@solidjs/web': '>=2.0.0' solid-js: '>=2.0.0' - '@solidjs/web@2.0.0-rc.6': - resolution: {integrity: sha512-JgQ2NCjygQpZizZrVjcwPqH3dhIZQZoMRGoGSC6+Tr622cvbuXthDC3pKdNMsjCKCVp19UbT0kkPX15FOdT0pw==} + '@solidjs/web@2.0.0-rc.7': + resolution: {integrity: sha512-qsKKWR4PzzPw8ZGFR0Oc2776G1ONMMBKPJWO3Opf3cWHoczgfhknW6NED0WtdCuR6T8usFTWCZgMM5Cc0h14Dg==} peerDependencies: - solid-js: ^2.0.0-rc.6 + solid-js: ^2.0.0-rc.7 '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -2839,8 +2839,8 @@ packages: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} - solid-js@2.0.0-rc.6: - resolution: {integrity: sha512-Z/M8s9ypLBf+6Bl3AAb5upgkYCl73HkpM+UxdUJ5uFGctTwpOQVCM9Gpj3Mjbiaki270HHUfA3Z0Lyn4w+fDtg==} + solid-js@2.0.0-rc.7: + resolution: {integrity: sha512-3APJcwGbJ3YzXzPXwl0R3cAiogXLacXdXSFasdE2uw1Gzj5xqDW/0bJu4fs75KK5WzXg+JfpkcsxBjTxkbnLQQ==} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -4445,7 +4445,7 @@ snapshots: kleur: 4.1.5 yargs-parser: 20.2.9 - '@solidjs/babel-plugin@2.0.0-rc.6(@babel/core@7.29.7)(@tsrx/core@0.1.63)': + '@solidjs/babel-plugin@2.0.0-rc.7(@babel/core@7.29.7)(@tsrx/core@0.1.63)': dependencies: '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.18.6 @@ -4457,61 +4457,61 @@ snapshots: optionalDependencies: '@tsrx/core': 0.1.63 - '@solidjs/compiler-darwin-arm64@2.0.0-rc.6': + '@solidjs/compiler-darwin-arm64@2.0.0-rc.7': optional: true - '@solidjs/compiler-darwin-x64@2.0.0-rc.6': + '@solidjs/compiler-darwin-x64@2.0.0-rc.7': optional: true - '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.6': + '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.7': optional: true - '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.6': + '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.7': optional: true - '@solidjs/compiler-wasm32-wasi@2.0.0-rc.6': + '@solidjs/compiler-wasm32-wasi@2.0.0-rc.7': dependencies: '@emnapi/core': 1.11.3 '@emnapi/runtime': 1.11.3 '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) optional: true - '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.6': + '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.7': optional: true - '@solidjs/compiler@2.0.0-rc.6': + '@solidjs/compiler@2.0.0-rc.7': optionalDependencies: - '@solidjs/compiler-darwin-arm64': 2.0.0-rc.6 - '@solidjs/compiler-darwin-x64': 2.0.0-rc.6 - '@solidjs/compiler-linux-arm64-gnu': 2.0.0-rc.6 - '@solidjs/compiler-linux-x64-gnu': 2.0.0-rc.6 - '@solidjs/compiler-wasm32-wasi': 2.0.0-rc.6 - '@solidjs/compiler-win32-x64-msvc': 2.0.0-rc.6 + '@solidjs/compiler-darwin-arm64': 2.0.0-rc.7 + '@solidjs/compiler-darwin-x64': 2.0.0-rc.7 + '@solidjs/compiler-linux-arm64-gnu': 2.0.0-rc.7 + '@solidjs/compiler-linux-x64-gnu': 2.0.0-rc.7 + '@solidjs/compiler-wasm32-wasi': 2.0.0-rc.7 + '@solidjs/compiler-win32-x64-msvc': 2.0.0-rc.7 - '@solidjs/diagnostics@2.0.0-rc.6(vitest@4.1.11)': + '@solidjs/diagnostics@2.0.0-rc.7(vitest@4.1.11)': dependencies: - '@solidjs/signals': 2.0.0-rc.6 + '@solidjs/signals': 2.0.0-rc.7 optionalDependencies: vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(jsdom@26.1.0)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)) - '@solidjs/signals@2.0.0-rc.6': {} + '@solidjs/signals@2.0.0-rc.7': {} - '@solidjs/start-devtools@1.0.0-next.4(@solidjs/web@2.0.0-rc.6(solid-js@2.0.0-rc.6))(solid-js@2.0.0-rc.6)': + '@solidjs/start-devtools@1.0.0-next.4(@solidjs/web@2.0.0-rc.7(solid-js@2.0.0-rc.7))(solid-js@2.0.0-rc.7)': dependencies: - '@solidjs/web': 2.0.0-rc.6(solid-js@2.0.0-rc.6) - solid-js: 2.0.0-rc.6 + '@solidjs/web': 2.0.0-rc.7(solid-js@2.0.0-rc.7) + solid-js: 2.0.0-rc.7 - '@solidjs/testing-library@1.0.0-beta.2(@solidjs/web@2.0.0-rc.6(solid-js@2.0.0-rc.6))(solid-js@2.0.0-rc.6)': + '@solidjs/testing-library@1.0.0-beta.2(@solidjs/web@2.0.0-rc.7(solid-js@2.0.0-rc.7))(solid-js@2.0.0-rc.7)': dependencies: - '@solidjs/web': 2.0.0-rc.6(solid-js@2.0.0-rc.6) + '@solidjs/web': 2.0.0-rc.7(solid-js@2.0.0-rc.7) '@testing-library/dom': 10.4.1 - solid-js: 2.0.0-rc.6 + solid-js: 2.0.0-rc.7 - '@solidjs/web@2.0.0-rc.6(solid-js@2.0.0-rc.6)': + '@solidjs/web@2.0.0-rc.7(solid-js@2.0.0-rc.7)': dependencies: seroval: 1.5.6 seroval-plugins: 1.5.6(seroval@1.5.6) - solid-js: 2.0.0-rc.6 + solid-js: 2.0.0-rc.7 '@standard-schema/spec@1.1.0': {} @@ -5964,9 +5964,9 @@ snapshots: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 - solid-js@2.0.0-rc.6: + solid-js@2.0.0-rc.7: dependencies: - '@solidjs/signals': 2.0.0-rc.6 + '@solidjs/signals': 2.0.0-rc.7 csstype: 3.2.3 seroval: 1.5.6 seroval-plugins: 1.5.6(seroval@1.5.6) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7375893..1c2a955 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,27 +8,27 @@ allowBuilds: msw: true catalog: - solid-js: "^2.0.0-rc.6" - "@solidjs/web": "^2.0.0-rc.6" + solid-js: "^2.0.0-rc.7" + "@solidjs/web": "^2.0.0-rc.7" ignoredBuiltDependencies: - cypress minimumReleaseAgeExclude: - - '@solidjs/signals@2.0.0-beta.30 || 2.0.0-beta.31 || 2.0.0-beta.32 || 2.0.0-rc.0 || 2.0.0-rc.1 || 2.0.0-rc.2 || 2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' + - '@solidjs/signals@2.0.0-beta.30 || 2.0.0-beta.31 || 2.0.0-beta.32 || 2.0.0-rc.0 || 2.0.0-rc.1 || 2.0.0-rc.2 || 2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' - '@solidjs/start-devtools@1.0.0-next.1 || 1.0.0-next.2 || 1.0.0-next.3' - - '@solidjs/web@2.0.0-beta.30 || 2.0.0-beta.31 || 2.0.0-beta.32 || 2.0.0-rc.0 || 2.0.0-rc.1 || 2.0.0-rc.2 || 2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' - - '@solidjs/babel-plugin@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' - - '@solidjs/compiler@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' - - '@solidjs/compiler-darwin-arm64@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' - - '@solidjs/compiler-darwin-x64@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' - - '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' - - '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' - - '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' - - '@solidjs/compiler-wasm32-wasi@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' - - '@solidjs/diagnostics@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6' + - '@solidjs/web@2.0.0-beta.30 || 2.0.0-beta.31 || 2.0.0-beta.32 || 2.0.0-rc.0 || 2.0.0-rc.1 || 2.0.0-rc.2 || 2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' + - '@solidjs/babel-plugin@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' + - '@solidjs/compiler@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' + - '@solidjs/compiler-darwin-arm64@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' + - '@solidjs/compiler-darwin-x64@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' + - '@solidjs/compiler-linux-arm64-gnu@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' + - '@solidjs/compiler-linux-x64-gnu@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' + - '@solidjs/compiler-win32-x64-msvc@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' + - '@solidjs/compiler-wasm32-wasi@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' + - '@solidjs/diagnostics@2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7' - '@tsrx/core@0.1.63' - - solid-js@2.0.0-beta.30 || 2.0.0-beta.31 || 2.0.0-beta.32 || 2.0.0-rc.0 || 2.0.0-rc.1 || 2.0.0-rc.2 || 2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 + - solid-js@2.0.0-beta.30 || 2.0.0-beta.31 || 2.0.0-beta.32 || 2.0.0-rc.0 || 2.0.0-rc.1 || 2.0.0-rc.2 || 2.0.0-rc.3 || 2.0.0-rc.4 || 2.0.0-rc.5 || 2.0.0-rc.6 || 2.0.0-rc.7 - '@dom-expressions/babel-plugin-jsx@0.50.0-next.35 || 0.50.0-next.37 || 0.50.0-next.40 || 0.50.0-next.43' - '@dom-expressions/compiler-darwin-arm64@0.50.0-next.35 || 0.50.0-next.37 || 0.50.0-next.40 || 0.50.0-next.43' - '@dom-expressions/compiler-darwin-x64@0.50.0-next.35 || 0.50.0-next.37 || 0.50.0-next.40 || 0.50.0-next.43' From a810d09eef0877c35a07259f0c0c494e8be23b4c Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Tue, 8 Sep 2026 10:16:15 -0700 Subject: [PATCH 3/3] fix: inline solid-js and @solidjs/web in dev server environments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit solid 2.0.0-rc.7 adds `dist/server.dev.*` builds to solid-js and @solidjs/web, selected by the `development` export condition nested under `node`/`worker`/`deno` (solidjs/solid 80ff52e). The plugin already prepends `development` to `resolve.externalConditions`, but that only reaches the imports Vite's module runner resolves itself: an externalized package's OWN imports go through Node with Node's conditions. Under `vite dev` the runner therefore loaded @solidjs/web's dev copy, whose native `import "solid-js"` landed on solid-js's PROD copy, while the app's own `solid-js` import was the runner's DEV copy — two instances of the framework. renderToStream installed the asset resolver on one sharedConfig and lazy() read the other, so every dev SSR page with a lazy() component failed with "lazy() called with moduleUrl … but no asset manifest is set" (ssr, css-matrix, start-ssr and start-client dev modes all red against rc.7); owner tracking, request events and hydration keys were split the same way. rc.6 had no solid-js dev server build, so both paths resolved to the same file and the split was invisible. The two core packages now go into `resolve.noExternal` for every server environment the plugin injects dev mode into, so all resolutions — theirs included — use the environment's conditions and one dev build is loaded end to end (verified with a Node resolve hook: zero native loads of any solid dist file). Vitest projects keep managing their own inlining via `test.server.deps`, and a host that set `noExternal: true` is untouched. Also drops the `ssr: { external: ['solid-js'] }` override the ssr and css-matrix examples carried as a TEMPORARY workaround for a since-removed workspace link; it now fights the plugin's default. Co-authored-by: Cursor --- .changeset/dev-ssr-inline-core.md | 5 +++++ examples/css-matrix/vite.config.ts | 6 ------ examples/ssr/vite.config.ts | 6 ------ src/index.ts | 27 +++++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 .changeset/dev-ssr-inline-core.md diff --git a/.changeset/dev-ssr-inline-core.md b/.changeset/dev-ssr-inline-core.md new file mode 100644 index 0000000..e399931 --- /dev/null +++ b/.changeset/dev-ssr-inline-core.md @@ -0,0 +1,5 @@ +--- +'@solidjs/vite-plugin': patch +--- + +Dev servers now inline `solid-js` and `@solidjs/web` into every server environment instead of externalizing them. `resolve.externalConditions` only governs the imports Vite's module runner resolves itself; an externalized package's own imports are resolved by Node with Node's conditions, never `development`. Since solid 2.0.0-rc.7 both core packages ship a `dist/server.dev.*` behind that condition, so under `vite dev` the framework split in two: the app's `solid-js` was the runner's dev copy while `@solidjs/web`'s `import "solid-js"` landed on Node's production copy. `renderToStream` installed the asset resolver on one `sharedConfig` and `lazy()` read the other — every dev SSR page with a `lazy()` component failed with `lazy() called with moduleUrl "…" but no asset manifest is set` — and every other module-level singleton (owner tracking, request events, hydration keys) was divided the same way. With the two packages in `resolve.noExternal` every resolution, theirs included, goes through the environment's conditions and a single dev build is loaded end to end. Applies whenever the plugin injects dev mode into a server environment; vitest projects (which manage their own inlining) and hosts that set `noExternal: true` are left as they are. diff --git a/examples/css-matrix/vite.config.ts b/examples/css-matrix/vite.config.ts index 7a67ca3..8e28e1a 100644 --- a/examples/css-matrix/vite.config.ts +++ b/examples/css-matrix/vite.config.ts @@ -61,12 +61,6 @@ export default defineConfig({ solidPlugin({ compiler: 'native', ssr: true }), bundleChunksProbe(), ], - // TEMPORARY: the workspace links solid-js to a sibling worktree (see - // pnpm-workspace.yaml), which stops Vite from externalizing it in SSR and - // splits it into two instances (bundled app copy vs the one the external - // @solidjs/web loads). Force it external to match published-package - // behavior; remove together with the workspace link. - ssr: { external: ['solid-js'] }, build: { manifest: true, rollupOptions: { diff --git a/examples/ssr/vite.config.ts b/examples/ssr/vite.config.ts index 2f9202f..61fc53a 100644 --- a/examples/ssr/vite.config.ts +++ b/examples/ssr/vite.config.ts @@ -3,12 +3,6 @@ import solidPlugin from '@solidjs/vite-plugin'; export default defineConfig({ plugins: [solidPlugin({ compiler: 'native', ssr: true })], - // TEMPORARY: the workspace links solid-js to a sibling worktree (see - // pnpm-workspace.yaml), which stops Vite from externalizing it in SSR and - // splits it into two instances (bundled app copy vs the one the external - // @solidjs/web loads). Force it external to match published-package - // behavior; remove together with the workspace link. - ssr: { external: ['solid-js'] }, build: { manifest: true, rollupOptions: { diff --git a/src/index.ts b/src/index.ts index 83bf9fd..7dcbf51 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1108,6 +1108,33 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { 'development', ...(config.resolve.externalConditions ?? defaultExternalConditions), ]; + + // `externalConditions` only reaches the imports the module runner + // resolves itself. An externalized package's OWN imports are resolved + // by Node, with Node's conditions — never `development`. Since + // solid 2.0.0-rc.7 both `solid-js` and `@solidjs/web` ship a + // `dist/server.dev.*` behind that condition, so leaving them external + // splits the framework in two under `vite dev`: the app's `solid-js` + // is the runner's dev copy while `@solidjs/web`'s `import "solid-js"` + // lands on Node's prod copy. `renderToStream` then installs the asset + // resolver on one `sharedConfig` and `lazy()` reads the other ("no + // asset manifest is set"), with every other module-level singleton + // (owner tracking, request events, hydration keys) split the same + // way. Inlining the two core packages makes every resolution — theirs + // included — go through the environment's conditions, so one dev + // build is loaded end to end. Framework packages that declare the + // `solid` export condition are already inlined via vitefu below and + // reach the same copy. Vitest projects manage their own inlining + // (`test.server.deps` above) and are left alone, as is a host that + // set `noExternal: true` (everything is inlined already). + if (!isTestMode && config.resolve.noExternal !== true) { + const noExternal = config.resolve.noExternal; + config.resolve.noExternal = [ + ...(Array.isArray(noExternal) ? noExternal : noExternal ? [noExternal] : []), + 'solid-js', + '@solidjs/web', + ]; + } } // Set resolve.noExternal and resolve.external for the SSR environment.