Releases: solidjs/solid-vite-plugin
Release list
@solidjs/vite-plugin@3.0.0-next.34
Minor Changes
- da12802: New
diagnosticsoption (dev serve only): injects a client module that installs the in-page bridge from the app's own@solidjs/diagnosticsand serves a/__solid/diagnosticsendpoint on the dev server. Out-of-process consumers (agents, tests, curl) drive capture sessions (begin/end),whyDidRun, and cost queries over the Vite WebSocket. Works for plain index.html apps (transformIndexHtml injection) and start mode (generated/custom client entry injection).@solidjs/diagnosticsis a type-only dependency of the plugin; the runtime bridge always comes from the app's installed copy. - da12802: Move to the renamed Solid 2.0 compiler packages: the native JSX/directives/lazy/refresh compiler is now
@solidjs/compiler(was@dom-expressions/compiler) and the Babel escape hatch is@solidjs/babel-plugin(wasbabel-preset-solid— now a plugin rather than a preset, hosted inpluginswith the same pass order: user plugins run before it, user presets after). Both backends now bake in the Solid defaults (moduleName: "@solidjs/web", the control-flowbuiltIns,contextToCustomElements,wrapConditionals), so the plugin only passes the posture it actually decides (generate/hydratable/dev/serverComponents) plus usersolidoptions, which override the built-in defaults exactly as before.
Patch Changes
- f0412e6: The
server-only/client-onlyboundary guard no longer crashes when
this.environmentisn't available on resolve hooks, and now detects the
target environment through the samegetEnvironmentConsumerhelper used
by the rest of the plugin, falling back to the resolve hook'sssrflag
when the environment is absent.
@solidjs/vite-plugin@3.0.0-next.33
Patch Changes
- c3d465a:
serverFunctions.componentsnow enables the SSR behavior-claims transform: ref and event-handler positions on intrinsic elements compile to guarded claim holes instead of dropping, so client behavior declared in server component markup survives serialization and morphs. SSR-only by construction (the dom generate ignores the flag); apps without the flag compile byte-for-byte as before. Compiler floor moves to@dom-expressions/compiler@0.50.0-next.44, which ships theserverComponentstransform option and its types.
@solidjs/vite-plugin@3.0.0-next.32
Patch Changes
- 2436bf5: Wire the Start development error boundary into server rendering and support direct
@solidjs/start-devtoolsimports in apps with custom server and client entries. - 473afd2: Start devtools are no longer enabled when
@solidjs/start-devtoolsis not installed. Detection falls back to resolving the package from the plugin's own file (for pnpm-isolated installs where it is only a dependency of the plugin), but the package is declared an optional peer dependency of the plugin, so when it is absent Vite answers that resolution with its__vite-optional-peer-dep:stub instead ofnull. The plugin took the stub as a successful resolution, wrapped the generated client entry inDevToolbar, and the browser failed withThe requested module '/@id/__vite-optional-peer-dep:@solidjs/start-devtools:@solidjs/vite-plugin' does not provide an export named 'DevToolbar'. The stub is now treated as "not installed". - 791ae3e: Require Vite 8 or newer and remove the Vite 6 and 7 compatibility paths.
@solidjs/vite-plugin@3.0.0-next.31
Minor Changes
- 8459560: New
start.devtoolsoption: a development toolbar with runtime errors and a
server function inspector, backed by the new optional-peer package
@solidjs/start-devtools. By default the toolbar turns on invite dev
whenever the package resolves (install it as a dev dependency) and stays off
otherwise;start: { devtools: true }makes the package required (a missing
install becomes an error) andstart: { devtools: false }opts out entirely.
Generated client entries wrap the app in the toolbar'sDevToolbarcomponent,
authored client entries get an injected mount import instead, and either way
the wiring is dev-serve-only codegen — production builds and previews contain
none of it. The package itself is resolved from the app graph first and from
the plugin's own location as a fallback, and the virtual toolbar modules
delegate their imports to that captured resolution, so pnpm-isolated installs
work without the package being hoisted to the app root.
Patch Changes
- c8615ed: Apply the request CSP nonce to start mode's generated client-entry script.
- 7c10d3b: Add a generic production error boundary to generated Start entries. It returns a 500 response for uncaught SSR errors and provides a fallback for uncaught client errors. Set
start.errorBoundarytofalsewhen application middleware owns error handling. - e607db7: Add
start.css.filterto control which module graphs are traversed while collecting development CSS.excludeprunes matching graphs (defaults to/node_modules/; providing one replaces the default), andincludeopts matching files in on top of that baseline — e.g.{ include: /node_modules\/some-ui-lib/ }server-inlines that dependency's CSS in dev. A file matching both patterns stays excluded. - 450d0e5: Add Vite 9 forward compatibility by using the per-environment consumer in plugin hooks and accepting Vite 9 as a peer.
@solidjs/vite-plugin@3.0.0-next.30
Patch Changes
- fbb5239: Unlock the @dom-expressions/compiler dependency from the exact 0.50.0-next.40 pin to the
^0.50.0-next.43range — floors at next.43, auto-graduates to later next.N and stable 0.50.0, matching how babel-preset-solid is ranged. Absorbs the .41–.43 compiler fixes: dedicated<!>insertion markers for components boxed by static text (solidjs/solid#3004, content no longer lands after the trailing text),$keyon intrinsic server JSX compiling to a_keyattribute so the frame morph matches keyed elements by key instead of position,transformLazyannotating the module-URL placeholder inlazy()'s third argument for solid-js 2.0's{ export }options bag (solidjs/solid#3011), HTML-escaping of static template-literal parts in attribute/style values, innerHTML/textContent holes no longer taking the_$scopeid reservation (solidjs/solid#3015), and the Rust 1.95 / Oxc 0.144 toolchain upgrade with the WASI linking fix. - 5543386: Fixes the dev server crashing on every request under https/HTTP/2. Vite serves
server.httpsthrough an HTTP/2 server (with HTTP/1 fallback), and the plugin's Node→web request bridge copied the h2 pseudo-headers (:path,:method,:authority,:scheme) intoHeaders, which throwsTypeError: ":path" is an invalid header name— so start-mode SSR and server functions 500'd on every request overvite devwith https. Pseudo-headers are now skipped and the host derives fromHostor the h2:authority. Alongside it, three more bridge hardenings (techniques referenced from srvx's Node adapter, h3js/srvx):request.urlnow sayshttps:on TLS sockets instead of alwayshttp:(secure-cookie logic, absolute redirects, and origin checks in app code saw the wrong protocol), client disconnects now fire the request'sAbortSignalso handlers can cancel streamed renders and in-flight work, and HEAD requests end immediately with the response body cancelled instead of pumping the whole (possibly endless) stream into Node's discarded HEAD writes.
@solidjs/vite-plugin@3.0.0-next.29
Minor Changes
- 24747b7:
options.event: the public wrapper→event extension seam on the generated handlers. Fields passed ashandleRequest(request, { event })(andhandleServerFunctionRequest(request, { event })on the standalone server-function handler, threaded through itscreateEventoption) spread into the request event at creation, so hosts and custom server entries can extend whatgetRequestEvent()answers with — no new convention beyondcreateRequestEvent's own init parameter. The conventional field isnativeEvent, the platform's raw request object: the plugin's own dev, preview, and server-function dev middlewares now passevent: { nativeEvent: req }(the NodeIncomingMessage), sogetRequestEvent().nativeEventreads the same undervite dev/vite previewas behind a production Node entry that passes it. The event shape itself is unchanged ({ request, locals, response }plus whatever the wrapper spreads in); nothing is attached to theRequest, and no client-address helper is added — on bare Node readevent.nativeEvent.socket.remoteAddress, behind a trusted proxy read the forwarding headers offevent.request.
Patch Changes
- 40c6865: Housekeeping: add the MIT LICENSE file the
licensefield has always declared but the repo never carried (#219), and documentvirtual:solid-manifestin the README — what it exports in dev (the live asset resolver) versus SSR builds (the baked client manifest with_base), and its role as the seam for frameworks doing their own asset gating (e.g. the TanStack Start integration). - ca4d221:
start.env: the generatedvirtual:env/servermodule no longer contains
top-level await, removing the esnext-target deploy requirement. Boot
validation used to conditionallyawaiteach validator result (Standard
Schema allowsvalidate()to return a Promise), which put a TLA in the
server env chunk whenever the schema hadserverkeys — and any
downstream bundler with a non-esnext target refuses a TLA chunk outright
(Nitro's node-server preset is the one that bites in practice), forcing
deployments to override the build target toesnext. Boot validation is
now fully synchronous with identical semantics: sameprocess.envread at
module init, same per-key report, same fail-loud-at-boot before any
importer's body runs, same frozenenvexport — and synchronous init is
the only shape that can keep the "validated before first use" guarantee,
since user server modules readenv.KEYat their own top level (deferring
the await to request entry cannot cover module-init consumers). The
tradeoff is explicit: async validators (e.g.z.string().refine(async ...)) are no longer supported forserverkeys — they are rejected at
config/build time with the fix in the message (they could only ever have
failed at deploy boot otherwise), and boot backstops with the same report
for schemas whose async-ness only surfaces on real values.clientkeys
keep async support: their values are baked at build time, where the plugin
awaits. The start-env suite now asserts every built server chunk
transforms under esbuild target es2020 (which rejects TLA at parse time —
exactly the check a downstream bundler applies) so this cannot regress. - 3257a97: Vite 8's dependency scan no longer breaks on
.tsxfiles (issue #262).
The plugin previously setoptimizeDeps.rolldownOptions.transform.jsx: 'preserve'to stop Rolldown from injectingreact/jsx-dev-runtime
imports during the scan — but the scanner re-parses the transformed
output as plain JS (import.meta.globhandling force-tags modules as
js, and even without glob the oxc-preserved JSX is re-parsed without
JSX enabled), so any.tsxwith JSX was a hardPARSE_ERROR: Unexpected JSX expressionthat aborted the whole scan. Every dependency was then
missed by pre-bundling and discovered at runtime instead ("new
dependencies optimized" mid-session re-optimize/reload — the classic
symptom for deps only reachable throughimport.meta.glob). The scan
transform now uses the classic JSX runtime, which lowers JSX to bare
React.createElementcalls without injecting any import: the scan
output is never executed, it only exists so rolldown can walk the import
graph, so the undefined identifier is harmless. With this, the scan
completes and glob-only dependencies are pre-bundled up front.
vite-plugin-solid@3.0.0-next.26
Patch Changes
-
6dbb7e8: Start mode's generated request handler now default-exports a Fetchable
{ fetch(request) }object alongside its namedhandleRequestexport.
Deployment integrations that follow the web-standard Fetchable convention
can consume the virtual handler or built server entry without a wrapper.
Thefetchmethod intentionally ignores provider arguments after the request
instead of forwarding them as Solid handler options.The normal
ssrenvironment now exposes that handler as itsindexservice
entry. Provider Vite plugins can adopt the same environment for development
and production withoutstart.external, a custom source entry, or explicit
Rollup input. Standalone builds continue to emitdist/server/server.js. -
06aadc6: Don't default
test.environmentto jsdom for vitest browser-mode projects (test.browser.enabled): vitest 4 probes for the environment's package at startup and sets a failing exit code when jsdom isn't installed, even though the suite runs (and passes) in the real browser. Browser-mode projects now fall back to vitest's own node default, which has no package probe — mirroring the existing jest-dom gate. Non-browser projects keep the jsdom default.
@solidjs/vite-plugin@3.0.0-next.28
Patch Changes
- fc5050f: update to solid 2.0.0-rc.0 — the solid-js/@solidjs/web peer ranges and the babel-preset-solid dependency move from
>=2.0.0-beta.32 <2.0.0-experimental.0to^2.0.0-rc.0, admitting the rc line (which the old experimental-capped upper bound excluded, sinceexperimentalsorts beforerc), still flooring above the hazardous 2.0.0-experimental.* publishes, and auto-graduating to stable 2.x
vite-plugin-solid@3.0.0-next.25
Patch Changes
- 11b87a1: Custom
extensionswork with the native compiler again. The native compiler picks its parser dialect from the file extension, so the transform builds a borrowed-extension filename (foo.mdx→foo.mdx.jsx, or.tsxwhen the extension is registered as TypeScript) for exactly this case — but only the lazy and refresh passes used it; the JSX transform itself still received the raw id, and@dom-expressions/compilerrejected it with "Unknown file extension" (#297).compiler: 'babel'was unaffected because that path names the parser plugins explicitly. The JSX transform now receives the same borrowed filename as the other native passes. - 74fb28b:
start.envnow rejectsserverschema keys that carry the public env prefix at config time. Vite bakes everyVITE_-prefixed variable (or whateverenvPrefixselects) into the browser'simport.meta.envregardless of which side of the schema declares it, soserver: { VITE_API_SECRET: ... }silently shipped the secret to every client through Vite's own channel — the build-time leak scan does flag server values that land in client chunks as literals, but dev has no scan at all, and short or colliding values can evade the literal match. The prefix rule was previously enforced one-way (client keys must have it); the reverse guard now fails fast with a rename message, mirroring the existing client-side guard. - 2e7b63c:
sendWebResponseno longer hangs forever when a client disconnects during backpressure. The write loop's'drain'wait had no other way to settle, but a response whose client already went away never emits'drain'— so every streamed SSR response aborted mid-stream (closed tab, slow mobile client) parked the promise chain, the body reader, and the Response object permanently, accumulating leaks over a start mode dev/preview session. The backpressure wait now also settles on'close'/'error'and the loop bails out early once the response is destroyed, letting the existing close handler's reader cancellation finish cleanup. - 84a4cab: docs: "turnkey" is now "start mode" across user-facing language — the JSDoc on
ssr/start/serverFunctionsand onStartOptions/ServerFunctionsOptions, the READMEs, and the config-time error/warning strings all say start mode (SSR start mode / client start mode; the server-function dev middleware is "built-in", since it works withoutstart). The components-without-start warning also names the right option (start, notapp). Prose citing Solid 2.0 beta versions is reworded version-neutrally now that 2.0 is past beta — published dependency ranges are untouched.
vite-plugin-solid@3.0.0-next.24
Patch Changes
-
dea55b3: Base-path and lazy-asset URL handling fixed across the turnkey surfaces (#298, #299, #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 injected
scan entries before any directive transform runs, so it walks straight
through'use server'modules into genuinely server-only code — and the
server-onlymarker's client-graph guard treated that as a violation,
failing the whole scan on every coldvite devstart ("Failed to run
dependency scan. Skipping dependency pre-bundling.") for any app whose
server functions reachserver-onlycode. The graph is legal once
transforms split it, so the guard now stands down on scanner resolves
(thescanflag Vite sets on plugin-container resolve options, both the
esbuild scanner in v6/7 and the rolldown one in v8) while still claiming
the specifier — the scanner must not chaseserver-only/client-onlyas
missing 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 aserver-onlymodule is still a build
error 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 environment
variables for both turnkey modes. A schema file at the project root —
env.ts/env.js, probed automatically (explicit viastart.env: './path',
off viafalse) — default-exports{ server, client }maps of Standard
Schema 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 — or
envPrefix— 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 the
file-loaded vars intoprocess.envitself, so templates drop the
process.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 own
restart-on-.env-change). In a build,clientfailures fail the build;
serverfailures only warn (a build machine may not have the production
secrets) 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 theVITE_prefix means. Server
values are NOT baked anywhere:virtual:env/serverreadsprocess.envat
server 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-buildgenerateBundlescan additionally fails
the build when a server var's literal value appears quoted in a client
chunk, and a generatedsolid-env.d.ts(written next to the schema) types
both 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'srunnerImport
for schema loading, runtime-read server values). Env is a turnkey feature:
withoutstartthere 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 ofssrto a newstartoption
(start: trueis the zero-config spelling, pure sugar forstart: {}—
both mean the identical turnkey mode with defaults), andssris a boolean
again with one meaning everywhere — "is the app server-rendered".
ssr: { ... }is now a config-time error with a migration message: write
start: { ... }(orstart: true) and setssr: true. Options are
otherwise unchanged (start.document,start.entryServer,
start.entryClient,start.middleware,start.external,start.appfor
the root component); a baressr: truewithoutstartkeeps the
transform-only behavior, andserverFunctionsstays orthogonal. The
SsrOptionstype is renamed toStartOptions.The reason for the split is the new turnkey client mode:
startwithout
ssr: trueserves the same conventions client-only. Dev streams the
rendered document shell (without the app — history-fallback semantics,
entry CSS inlined) and the generated client entryrender()s the app into
document.body;vite buildprerenders the shell once through t...