From 38434e1113cafa59ad92ed1cf1e1357c7882e1a0 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Wed, 9 Sep 2026 17:02:11 -0700 Subject: [PATCH] fix: name the client entry in virtual:solid-manifest instead of guessing it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With filesystem-routing's `fileRoutes({ routers: { client }, buildInputs: 'client' })` every route module is a configured client build input, and since #347 (next.40) those manifest records rightly keep `isEntry`. The generated start-mode handler resolved the client entry by taking the first `isEntry` record in manifest key order, so a `src/routes/...` key sorting ahead of `virtual:solid-ssr-entry-client.tsx` won: the document booted the route chunk (no hydration) and linked the route's CSS while the entry graph's global stylesheet was never linked (#353). The plugin knows which input is the application entry — start mode injects it — so the manifest module now says so: `_entry` carries the entry's key (the start-mode entry reported by startServe, or the single configured input outside start mode) and the entry record is serialized first, so @solidjs/web's registerEntryAssets (first-`isEntry` scan) links the same chunk's CSS/preload graph. resolveClientEntry() reads `_entry` and keeps the scan only as the fallback for hand-rolled manifests. Other configured inputs keep `isEntry`. Regression test: start-ssr `extra-input` mode builds with an additional configured client input that App.tsx also lazily imports and asserts the entry stylesheet is linked, the module script is the real entry chunk, and the extra input's chunk is neither. Closes #353 --- .changeset/manifest-client-entry-identity.md | 5 + examples/start-ssr/src/App.tsx | 12 ++ examples/start-ssr/src/ExtraInput.css | 3 + examples/start-ssr/src/ExtraInput.tsx | 13 ++ examples/start-ssr/test/run.mjs | 124 ++++++++++++++++++- examples/start-ssr/vite.config.ts | 11 ++ src/index.ts | 99 ++++++++++++++- src/ssr/index.ts | 21 +++- 8 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 .changeset/manifest-client-entry-identity.md create mode 100644 examples/start-ssr/src/ExtraInput.css create mode 100644 examples/start-ssr/src/ExtraInput.tsx diff --git a/.changeset/manifest-client-entry-identity.md b/.changeset/manifest-client-entry-identity.md new file mode 100644 index 0000000..e09fd85 --- /dev/null +++ b/.changeset/manifest-client-entry-identity.md @@ -0,0 +1,5 @@ +--- +'@solidjs/vite-plugin': patch +--- + +Fix the start-mode handler booting the wrong chunk when the client build has several configured inputs (#353, a regression of 3.0.0-next.40 / #347). With filesystem-routing's `fileRoutes({ routers: { client }, buildInputs: 'client' })` every route module is a `build.rollupOptions.input`, and since #347 those records rightly keep `isEntry` in `virtual:solid-manifest`. The generated handler resolved the client entry by scanning for the first `isEntry` record, so a route key sorting ahead of the plugin's own `virtual:solid-ssr-entry-client.tsx` won: the document's ``), + `stylesheets: ${extraStylesheets.join(', ')}`, + ); + } catch (e) { + record(mode, 'run', 'mode completed', false, String(e)); + } finally { + // Leave dist in the standard state for anyone poking at it. + try { + execSync('pnpm run build', { cwd: exampleDir, stdio: 'pipe' }); + } catch {} + } +} + // Builder-mode preparation: BUILD_PRE_WIPE=1 installs a nitro-v3-shaped // host in vite.config.ts — a pre-order `buildApp` hook that rm -rf's dist // before anything builds (nitro's `nitro:prepare`) and a post-order @@ -4288,6 +4406,7 @@ const ALL_MODES = [ 'base', 'builder-order', 'builder-prepare', + 'extra-input', 'frames', 'babel-hmr', 'external', @@ -4311,6 +4430,7 @@ for (const mode of modes) { else if (mode === 'base') await runBaseMode(); else if (mode === 'builder-order') await runBuilderOrderMode(); else if (mode === 'builder-prepare') await runBuilderPrepareMode(); + else if (mode === 'extra-input') await runExtraInputMode(); else if (mode === 'frames') await runFramesMode(); else if (mode === 'babel-hmr') await runBabelHmrMode(); else if (mode === 'external') await runExternalMode(); diff --git a/examples/start-ssr/vite.config.ts b/examples/start-ssr/vite.config.ts index 1907049..9065227 100644 --- a/examples/start-ssr/vite.config.ts +++ b/examples/start-ssr/vite.config.ts @@ -54,6 +54,10 @@ import solidPlugin from '@solidjs/vite-plugin'; // - SOLID_SERVER_COMPONENTS points the generated entries at the // server-components page and flips `serverFunctions: { components: true }` // (frames mode) — the one-line enablement under test. +// - EXTRA_CLIENT_INPUT=1 (extra-input mode) lists src/ExtraInput.tsx — a +// module App.tsx also lazily imports — as a further client build input, +// the shape filesystem-routing's `buildInputs` produces for every route +// module (#353). Vite merges the plugin's injected entry into this array. const jsxCompiler = process.env.SOLID_JSX_COMPILER === 'babel' ? ('babel' as const) : ('native' as const); const serverComponents = !!process.env.SOLID_SERVER_COMPONENTS; @@ -108,6 +112,13 @@ export default defineConfig({ }, } : {}), + ...(process.env.EXTRA_CLIENT_INPUT + ? { + environments: { + client: { build: { rollupOptions: { input: ['src/ExtraInput.tsx'] } } }, + }, + } + : {}), ...(process.env.BUILD_SSR_FIRST ? { builder: { diff --git a/src/index.ts b/src/index.ts index 7dcbf51..37eae3d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -716,6 +716,80 @@ function normalizeEmittedLazyEntries( } } +/** + * The manifest key of THE client entry — the chunk whose `