Version Packages (next) - #296
Merged
Merged
Conversation
github-actions
Bot
force-pushed
the
changeset-release/next
branch
from
August 7, 2026 17:40
0e08e2a to
e54f582
Compare
github-actions
Bot
force-pushed
the
changeset-release/next
branch
from
August 10, 2026 19:42
e54f582 to
6e1c6d4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated.
nextis currently in pre mode so this branch has prereleases rather than normal releases. If you want to exit prereleases, runchangeset pre exitonnext.Releases
vite-plugin-solid@3.0.0-next.24
Patch Changes
dea55b3: Base-path and lazy-asset URL handling fixed across the turnkey surfaces (Dev SSR lazy asset URLs ignore Vite base and root-external modules #298, SSR lazy asset lookup drops module query strings #299, vite preview misses server-function endpoint with a non-root base #300). Vite's base middleware strips the configured
basefromreq.urlbefore post middlewares run, but the built handler compares the request pathname against the base-prefixed server-function endpoint and hands the URL to application code — so under a non-rootbase,vite previewnever dispatched/base/_server(requests fell through to page rendering) and dev page SSR saw base-stripped URLs that production would never send. The plugin's node adapters (the preview middleware, the turnkey dev SSR middleware, and the server-function dev middleware when the stripped endpoint form matched) now restore the base before constructing the webRequest, so the handler sees production-shaped URLs on every surface. The dev asset resolver's lazy module URLs get the same treatment: they were emitted as"/" + key, which Vite rejects outside a non-root base and which mis-normalizes for root-external modules — they are now base-prefixed, and keys outside the Vite root (../…, e.g. sibling workspace packages) resolve to/@fs/URLs; the generatedvirtual:solid-manifestdev fallback mirrors the same logic. Finally, module query strings survive the lazy asset lookup:resolveLazyModuleUrlsand the SSR$$moduleUrlinjection kept only the queryless path while Rollup keys the facade chunk (and the Vite manifest entry) by the queried module id, solazy(() => import('./Panel.tsx?variant=a'))missed its production manifest entry and could load different plugin output in dev — the query is now part of the asset key end to end, matching the manifest and the dev URL.3e5d8ff: Boundary guard no longer aborts Vite's dependency scan. The dep scanner
(
vite:dep-scan) crawls the raw import graph from the plugin's injectedscan entries before any directive transform runs, so it walks straight
through
'use server'modules into genuinely server-only code — and theserver-onlymarker's client-graph guard treated that as a violation,failing the whole scan on every cold
vite devstart ("Failed to rundependency scan. Skipping dependency pre-bundling.") for any app whose
server functions reach
server-onlycode. The graph is legal oncetransforms split it, so the guard now stands down on scanner resolves
(the
scanflag Vite sets on plugin-container resolve options, both theesbuild scanner in v6/7 and the rolldown one in v8) while still claiming
the specifier — the scanner must not chase
server-only/client-onlyasmissing bare dependencies, which would abort the scan all the same. Real
dev and build module graphs resolve without the flag and stay fully
guarded: a client-side import of a
server-onlymodule is still a builderror naming the importer.
e180576: The dev-manifest bridge resolver no longer caches failed lookups. The convergence cache introduced for the nested-lazy render-pass fix stored the
nulla bridge failure resolves to, so one transient miss (dev server briefly unreachable, non-OK response) silently stripped that module's client assets — and its hydration preload entry — for the rest of the dev session. Only successful answers are cached now; failures keep logging loudly and stay retryable, while in-flight dedupe still hands retries of the same render pass a stable promise, so convergence is unaffected.23965a2: The dev asset resolver now caches per module key and answers synchronously once a key's assets are known (in-flight walks are deduped; any watcher event drops the cache so dev CSS stays fresh). Server-side
lazy()re-requests a module's assets on every retry of a suspended render pass — the router's nested outlets re-create the component per retry — and an always-async resolver suspends every retry on a brand-new promise, so the pass never converges: any nested route hungvite dev, or overflowed the render stack (one resume closure nests per cycle) and the escaped rejection killed the dev server. The build manifest never looped because it answers synchronously; dev now matches it after the first resolution. The HTTP bridge resolver for isolated SSR runners (nitro dev worker, workerd) gets the same convergence cache.a2bd979: Turnkey dev middleware dispatches every request through the
start.middlewarechain — all methods and accept types, matching production and preview — instead of only HTML-accepting GETs. API routes (GET/POST exports served by a middleware like filesystem-routing'screateAPIHandler) and no-JS form POSTs now work undervite dev. Non-page requests the chain does not handle fall back to Vite's own pipeline (its 404) rather than getting a page rendered at them, and without a configured middleware nothing changes: non-page requests never leave Vite's pipeline.ec7543f: Doc examples for
serverFunctions.configureimportconfigureServerFunctionsServerfrom the type-correct@solidjs/web/server-functions/serversubpath (the base subpath's types are the client surface and don't declare it).6e2d526: Turnkey typed env (
start.env): first-party typed, validated environmentvariables for both turnkey modes. A schema file at the project root —
env.ts/env.js, probed automatically (explicit viastart.env: './path',off via
false) — default-exports{ server, client }maps of StandardSchema validators (zod, valibot, arktype, mixable per key; nothing imported
from the plugin), and the validated values come back through two typed
virtual modules:
virtual:env/server(every var, server module graphs only— a client-graph import is a hard error naming the importer) and
virtual:env/client(theVITE_-prefixed client side; the prefix — orenvPrefix— is enforced at config time).Validation is node-only and layered, against Vite's
loadEnvmerge of the.env*files withprocess.envwinning — and the plugin folds thefile-loaded vars into
process.envitself, so templates drop theprocess.env = { ...process.env, ...loadEnv(mode, root, '') }boilerplate.In dev every failure renders the error overlay with a per-key report and
.env*/schema edits revalidate live (surviving Vite's ownrestart-on-.env-change). In a build,
clientfailures fail the build;serverfailures only warn (a build machine may not have the productionsecrets) and boot validation enforces them.
Client values are baked as validated plain JSON — defaults applied,
coercions done, zero validator bytes in a client bundle (the
runtime-library alternative ships its validator to the browser; t3-env
costs ~13 kB gz of zod) — that's what the
VITE_prefix means. Servervalues are NOT baked anywhere:
virtual:env/serverreadsprocess.envatserver boot and validates through the user's own schema, imported into the
server bundle only. Platform-injected vars that don't exist at build time
work, secrets rotate without a rebuild, and no secret value exists in any
dist artifact; an invalid server environment fails boot with the same
per-key report. A client-build
generateBundlescan additionally failsthe build when a server var's literal value appears quoted in a client
chunk, and a generated
solid-env.d.ts(written next to the schema) typesboth virtual modules by inference from the user's own schema via the
Standard Schema output type.
Design credit: the feature's shape — the env.ts convention, the
virtual:env/*names (kept identical deliberately), baked client values,the leak-scan heuristics — follows @vite-env/core by pyyupsk (MIT,
https://github.com/pyyupsk/vite-env); the implementation is fresh on this
plugin's machinery (Standard Schema as the only contract, no zod/jiti
dependencies, consumer-based environment guarding, Vite's
runnerImportfor schema loading, runtime-read server values). Env is a turnkey feature:
without
startthere is no env layer. Seeexamples/start-env.1272d95: Turnkey per-request app setup:
start.setuppoints at a server-only module default-exporting(event, App) => Component | void | Promise<Component | void>, awaited by the generated server entry after the middleware chain dispatches to the page render and immediately beforerenderToStream. The seam routers with async per-request preparation need for SSR (create a router bound to the request,await router.load(), then render) — return a component to render in the app's place, or nothing to render<App />unchanged. The hook sees the same request event middleware decorated and runs inside the request scope. Zero-config entries are byte-identical without the option; authored server entries ownrender()already, so combining them is a config error.8b36370: Breaking (turnkey config reshape): Start is now a mode of the plugin. The
turnkey options move from the object form of
ssrto a newstartoption(
start: trueis the zero-config spelling, pure sugar forstart: {}—both mean the identical turnkey mode with defaults), and
ssris a booleanagain with one meaning everywhere — "is the app server-rendered".
ssr: { ... }is now a config-time error with a migration message: writestart: { ... }(orstart: true) and setssr: true. Options areotherwise unchanged (
start.document,start.entryServer,start.entryClient,start.middleware,start.external,start.appforthe root component); a bare
ssr: truewithoutstartkeeps thetransform-only behavior, and
serverFunctionsstays orthogonal. TheSsrOptionstype is renamed toStartOptions.The reason for the split is the new turnkey client mode:
startwithoutssr: trueserves the same conventions client-only. Dev streams therendered document shell (without the app — history-fallback semantics,
entry CSS inlined) and the generated client entry
render()s the app intodocument.body;vite buildprerenders the shell once through the builthandler into
dist/client/index.html(hashed entry script + entry CSSlinks) and emits a purely static
dist/client—dist/serveris kept onlywhen
serverFunctionsneeds it for the endpoint, and pages stay static.Client code compiles exactly like a plain SPA (
generate: 'dom',non-hydratable); only the document shell goes through the SSR transforms.
Server-only options (
start.entryServer,start.external, conventionalsrc/entry-server.*files) are documented no-ops in client mode, soflipping a project between SPA and SSR is toggling the one
ssrboolean —same App, same Document, same server functions (see
examples/start-client,whose test flips the same app between the modes).
410c95f: Vitest always gets the client posture, regardless of the app's
ssrflag: test-mode transforms compile non-hydratable (dom codegen under a DOM environment — nothing hydrates in a test), thebrowserexport condition applies so solid resolves its client builds, andtest.environmentdefaults tojsdomfor server-rendered apps too. DOM component tests in anssr: trueapp work without thessr: mode !== 'test'workaround; explicittest.environment: 'node'still gets server codegen forrenderToString-style tests.ad11469: Per-vitest-project server test posture: a project that sets
test.environment: 'node'(or'edge-runtime') explicitly gets the server posture end to end — server export conditions (isServertrue), ssr codegen, and the framework inlined so the whole graph (request-event storage included) resolves into one server-build instance despite the workspace worker pool's root-derived native--conditions. Server-runtime unit suites (server functions, sessions) need nodeps.inline/alias workarounds, and DOM (jsdom) and node projects coexist in one workspace.