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/dev-ssr-inline-core.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion .changeset/start-render-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Loading>` 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 `<Loading>` 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).
13 changes: 4 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 0 additions & 6 deletions examples/css-matrix/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
6 changes: 0 additions & 6 deletions examples/ssr/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Loading
Loading