Releases: solidjs/solid-vite-plugin
Release list
@solidjs/vite-plugin@3.0.0-next.44
Minor Changes
-
22858a3:
start.node: the build emits a ready-to-run Node server. Withstart: { node: true }the ssr build writesdist/server/node.jsbesideserver.js—node dist/server/node.js(envPORT, default 3000, andHOST) serves the client build statically (files underbuild.assetsDirasCache-Control: public, max-age=31536000, immutable, everything elsepublic, max-age=0, must-revalidatewithLast-Modified; a reasonable MIME table,HEAD, dot-segment paths and..traversal refused) and hands every other request tohandleRequestwith the raw Node request asnativeEvent, sogetRequestEvent().nativeEventanswers the same as undervite devandvite preview. The node<->web bridge is the plugin's ownsrc/http.ts— the code the dev and preview middlewares already run (HTTP/2 pseudo-headers,https:on TLS sockets, client disconnects as the request'sAbortSignal, HEAD short-circuit,set-cookiesplit, backpressure that also settles on close) — shipped as a separate build artifact of the package (dist/node-entry.mjs) that the plugin reads at build time and emits under a small generated header carrying the emit-time constants (client dir relative to the server dir,assetsDir,base, the mode). The file is ESM with no dependency beyondnode:*and./server.js, listens only when run directly, and exportslistener(the(req, res)function, mountable intohttp.createServer, Express, Fastify),createListener({ static?, event? })—static: falseleaves files and the client-modeindex.htmlfallback to a framework such asexpress.staticor a CDN and keeps only the bridge;event: (req) => fieldsmerges extra request-event fields over{ nativeEvent: req }— andserve({ port?, host?, static?, event? }).Why: the fullstack templates shipped a hand-written
server.jswhose bulk was this generic Node bridge, copied into every scaffold and baked into the CLI, so bridge fixes never reached users — and it looked custom-made. Node is the one mainstream runtime without a fetch-shaped server API (Workers, Deno, Bun, Netlify, Nitro consume the server bundle's{ fetch }directly), so the gap is Node-only and belongs in the build output. It is astart.*option, notssr.*(ssrstays boolean-only), and applies to both start modes: SSR mode renders pages; client mode withserverFunctions(which keepsdist/server) serves the static client with anindex.htmlhistory fallback for HTML navigations plus the endpoint.node.jsis an emitted asset, never a second build input —server.jsand itshandleRequest/{ fetch }contracts are unchanged, and nothing changes without the option. Where no server bundle exists (client mode withoutserverFunctions, orstart.external) the build warns and emits nothing. Compression/proxy stance unchanged: plain HTTP, put a reverse proxy or CDN in front. Follow-ups this unlocks: the templates dropserver.jsand point theirstartscript atnode dist/server/node.js; create-solid drops its bakedSERVER_JSconstant.
Patch Changes
-
7fa5aa3: Avoid overriding environments configured in Vitest workspaces and projects with the
jsdomdefault (forward-port of #323 by @carloitaben, fixes #205). A root config that definestest.projects(or the pre-Vitest-4test.workspace) runs no tests itself, so it no longer getstest.environment: 'jsdom'injected — which made Vitest probe for (and prompt to install) jsdom at startup even when every project runs under node or in browser mode. Each project keeps controlling its own environment. -
d30f051: Only inject the
@testing-library/jest-domVitest setup file when the package resolves from the project root (forward-port of #364 by @brenelz, fixes #231). Previously the check ran from the plugin's own location, so with pnpm a transitive jest-dom (for example via Storybook) made Vitest fail withFailed to load url .../@testing-library/jest-dom/vitest. The probe now walksnode_modulesup from the Vite root the way Vitest resolves baresetupFiles— deliberately ignoringNODE_PATH, which pnpm's bin shims (pnpm vitest) point at the hoisted virtual store where every transitive dependency is reachable. -
2e79544: Require solid-js / @solidjs/web 2.0.0-rc.9 (peer floor) and compile with @solidjs/compiler / @solidjs/babel-plugin rc.9 — runtime and compiler move in lockstep. The rc.9 compilers emit output only the rc.9 runtime understands, so this floor is a hard requirement, not policy: native elements with several spread sources compile to the runtimes' array form (
spread(el, [a, b], …)on the client,ssrElement(tag, [a, b], …)on the server — nomergeProps()proxy, no memo, no hydration id; #3418/#3419/#3423) which only rc.9's@solidjs/webaccepts; delegated event handlers move off Solid 1's$$<type>element key onto_$$<type>in both compiled output and the runtime's document delegate, so an rc.8 runtime would never fire an rc.9-compiledonClick(and vice versa); and undercomponentNames— which the plugin already turns on for its dev and observe postures — SSR output keepscreateComponent(Comp, props, "Comp")so the server runtime's observe/dev tier labels the owner and a server finding'sownerPathreads<App> › <Page>like the client's. Nothing in the plugin itself had to change for rc.9: the newobserve/developmentexport conditions on@solidjs/web's server-functions and frames client entries (andobserveon every server entry) are picked up by the condition lists the plugin already installs, and the newsolid-js/internalsubpath is covered by the existingsolid-jsinlining. -
256c25d:
start.instrument: a server-only module the plugin runs to completion before anything else in the server graph loads — the app, the middleware,@solidjs/web, every dependency. The seam for instrumentation that must patch the runtime before the modules it patches are loaded (an APM's OpenTelemetry setup, a profiler, amodule.registerhook), honored on every surface:vite dev,vite build,vite preview, and a host consuming the handler entry. Replaces the per-hostnode --import instrument.mjsdance.Import order cannot do this in ESM — static imports are hoisted and evaluated in dependency order — so the generated handler entry becomes
await import(instrument); await import(handler), with the handler's surface (handleRequest, thefetchdefault) re-declared by name. The module may be async and needs no exports; the server build must keep code splitting on (the default).Also: the
componentNamesnote in the compiler options no longer calls the labels DOM-only — the SSR generate emits them too from the compilers that carry solidjs/solid#3441 (2.0.0-rc.9), and the start-ssr suite gains anobservemode that asserts anobserve: trueproduction build resolves the observe artifacts and carries component labels (the SSR half asserted once the workspace rides an rc that emits them).
@solidjs/vite-plugin@3.0.0-next.43
Patch Changes
- 4fb1af3: Honor the host's
resolve.noExternalpatterns when adding vitefu's externals to the SSR environment. The plugin already refused to re-externalize anythingnoExternalinlines, but it compared literal names only, while Vite treats string entries as picomatch patterns and RegExp entries as tests (createFilter(undefined, noExternal, { resolve: false })). Since 3.0.0-next.41 the crawl also reaches the packages that consume the Solid runtime, and their non-Solid dependencies land inssr.external— so a host that inlines its packages by pattern (TanStack Start's@tanstack/start**, whose@tanstack/start-server-coreresolves its#tanstack-*imports only when Vite processes it) saw them re-externalized, andvite devfailed withERR_PACKAGE_IMPORT_NOT_DEFINED: Package import specifier "#tanstack-router-entry" is not defined. The externals are now filtered with the same matcher Vite uses, and a single string or RegExpnoExternalvalue is kept instead of being dropped. - b173c94: New
observeoption: resolve Solid's observe builds for production observability.observe: trueadds theobserveexport condition to every environment — client and server, inlined and externalized (resolve.externalConditions), inlining the core runtime and its consumers for server builds the way the dev posture already does so one build is loaded end to end — and turns on the compiler'scomponentNamesoption, so component owner labels (<Home>) survive minification in diagnostics and attribution paths.componentNamesis also enabled under the dev posture, wherelazy()and HMR wrappers otherwise hide the tag name. Requires@solidjs/compiler/@solidjs/babel-plugin≥ 2.0.0-rc.8 (the release that adds the option).
@solidjs/vite-plugin@3.0.0-next.42
Patch Changes
- ed9b798: Allow
@testing-library/jest-domv7 in the optional peer dependency range (ports #287 frommain). The Solid 2.0 templates pin@testing-library/jest-dom@^7.0.0, and npm 7+ enforces peer ranges, so a cleannpm installof a freshly created project failed withERESOLVEagainst the^6.*range (solidjs/solid#3341). v7 keeps the@testing-library/jest-dom/vitestsubpath the plugin auto-injects intotest.setupFiles, so only the range changes.
@solidjs/vite-plugin@3.0.0-next.41
Patch Changes
- 38434e1: 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 abuild.rollupOptions.input, and since #347 those records rightly keepisEntryinvirtual:solid-manifest. The generated handler resolved the client entry by scanning for the firstisEntryrecord, so a route key sorting ahead of the plugin's ownvirtual:solid-ssr-entry-client.tsxwon: the document's<script type="module">pointed at the route chunk (the page never hydrated) and<head>linked that route's CSS while the entry graph's global stylesheet was never linked. The manifest module now names the client entry explicitly —_entrycarries its key (the entry start mode injects, or the single configured input outside start mode) and its record is serialized first — and the handler reads_entrybefore falling back to theisEntryscan for hand-rolled manifests.@solidjs/web'sregisterEntryAssets, which links the entry graph's stylesheets and modulepreloads by the firstisEntryrecord, therefore agrees on the same chunk. Other configured inputs keepisEntry; they are genuine entries, just not the one the document boots. - bd04c66: Packages that consume the Solid runtime without declaring a
solidexport condition are now inlined in dev server environments too, closing the remaining half of the two-instance split. Inliningsolid-jsand@solidjs/webfixes every resolution those two perform, and vitefu inlines packages that advertise asolidexport condition — but a package that does neither is still externalized, and Node resolves its ownimport "solid-js"without thedevelopmentcondition, so it loads the production server build while the inlined graph holds the dev one.@solidjs/metais the first-party example: it has nosolidcondition, so under solid-js 2.0.0-rc.7 an app rendering a<Title>still died inuseContexton a secondsharedConfigeven with the core packages inlined. The crawl now also classifies any package declaringsolid-jsor@solidjs/webin itsdependenciesorpeerDependenciesas a semi-framework package —ssr.noExternalwithoutoptimizeDeps.exclude, since these hold no raw Solid components — so third-party component libraries and metadata helpers reach the same copy as everything else. Gated on the dev-condition swap (and off under vitest, which manages inlining itself), leaving builds unchanged. Two guards keep the rule narrow: tooling that declaressolid-jsas a peer but never runs inside the SSR module runner —@solidjs/vite-pluginitself,vite,vitest,eslint-plugin-*,vite-plugin-*,prettier-plugin-*,@types/*— is skipped entirely (classifying the plugin would also crawl its dependencies and pre-bundle@babel/coreand@solidjs/babel-plugininto the browser'soptimizeDeps, several megabytes of dead weight per cold start); and thessr.externallist vitefu derives from framework packages' non-frameworkdependenciesis filtered against the finalnoExternallist, because Vite givesexternalprecedence — a framework package listing@solidjs/webunderdependencies(e.g.@tanstack/solid-router) would otherwise re-externalize a core the plugin just inlined and split the runtime again.
@solidjs/vite-plugin@3.0.0-next.40
Minor Changes
- 0985ce8:
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 therenderToStreamresult'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 takescreateSSRResponse's string path: the response head commits, the document gets the doctype and client-entry injection, and aLocationwritten 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;deferStreamis moot (everything defers). The per-request form follows themiddleware/setupconvention —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 passhandleRequest(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. Requiressolid-js/@solidjs/web^2.0.0-rc.7, which freezes the response head when the awaited render completes sohttpStatus/httpHeaderdeclarations reach the response (solidjs/solid#3292).
Patch Changes
-
a810d09: Dev servers now inline
solid-jsand@solidjs/webinto every server environment instead of externalizing them.resolve.externalConditionsonly governs the imports Vite's module runner resolves itself; an externalized package's own imports are resolved by Node with Node's conditions, neverdevelopment. Since solid 2.0.0-rc.7 both core packages ship adist/server.dev.*behind that condition, so undervite devthe framework split in two: the app'ssolid-jswas the runner's dev copy while@solidjs/web'simport "solid-js"landed on Node's production copy.renderToStreaminstalled the asset resolver on onesharedConfigandlazy()read the other — every dev SSR page with alazy()component failed withlazy() 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 inresolve.noExternalevery 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 setnoExternal: trueare left as they are. -
a3cc782: Fix
vite devbreaking after a mid-session dependency re-optimization when the development toolbar is installed. The generated entries'@solidjs/start-devtoolsimport reused the id captured when the toolbar was detected; in the client environment that id is the optimizer's pre-bundled URL, stamped with the browserHash of the pass that produced it. Any dependency discovered after the initial scan re-optimizes — the toolbar's chunks are re-emitted under new names and the hash moves on — and the frozen id kept the entry on the previous pass: its lazy chunks answered504 Outdated Optimize Depand the stale bundle brought a secondsolid-jsinstance into the page (hydration key misses,REACTIVITY_HALTED). The import is now resolved afresh on every request, so it always follows the current optimizer pass.The most common trigger is also removed: the agent diagnostics bridge (
@solidjs/diagnostics/browserand/protocol) reaches the page through a virtual module the dependency scanner never crawls, so its first load discovered the two imports and forced exactly that re-optimize + reload. The diagnostics plugin now pre-bundles them up front whenever the surface is enabled. -
c16985a: Provide the deployment secret to server builds (solidjs/solid#3239): the generated server-function handler module now leads with
globalThis.__SOLID_SECRET__ ??= "<random-per-build>", giving the runtime's encrypted no-JS flash cookie a key with zero configuration. One value is generated per plugin instance, so a production build bakes a single secret into the emitted server chunk (shared by every instance of that deployment) and a dev session holds one for its lifetime. Server output only — the handler module is already hard-gated against client graphs — and an explicitconfigureServerFunctionsServer({ secret })still outranks it. -
1f5f6ca: Never strip
isEntryfrom a genuine configured entry when reclassifying emitted lazy facade chunks. The normalization used to demote every chunk that is a dynamic-import target, which misfires once the real client entry absorbs a module that is also imported dynamically: with Solid 2,@solidjs/web/frames/clientlazily imports the serialization decoder, so a static import of@solidjs/web/serialization/decodeanywhere in the client graph merges the decoder into the entry chunk and the entry ends up listing itself underdynamicImports. Demoting it left the bundle andmanifest.jsonwith no entry at all ("No entry file found" in downstream manifest capture such as TanStack Start's). Chunks whose facade matches a configuredbuild.rollupOptions.input(or the defaultindex.html/ the start-mode client entry) now keepisEntryin the raw bundle and invirtual:solid-manifest, a chunk's dynamic import of itself is ignored, emittedlazy()facades are still reclassified, and a demotion the plugin cannot attribute to one of its own emitted chunks is reported with a warning describing the graph shape. The virtual manifest also repairsisDynamicEntryon lazy facades, which rolldown drops when syncinggenerateBundlemutations back.
@solidjs/vite-plugin@3.0.0-next.39
Patch Changes
- 9fbbef6: The persisted server-function manifest (
dist/client/.vite/solid-server-functions.json) now records every server function the client build can reach, by wire id, alongside the module list:{ modules: string[], functions: Array<{ id, name, module }> }. Build tooling that needs the client-reachable set — a static-site prerenderer verifying that each reachable function was captured at build time, for example — reads it from here instead of re-deriving it from compiled output. The previous array shape is still accepted when read (the type is exported asPersistedServerFunctionManifest).
@solidjs/vite-plugin@3.0.0-next.38
Patch Changes
- dfabe22: Auto-enable the agent diagnostics surface (dev serve only) when
@solidjs/diagnosticsis installed in the app — installing the dev dependency is now the whole setup. Thediagnosticsoption becomes an override:trueforces it on (erroring if the package is missing),falseopts out entirely, omitted auto-detects. Start mode's generated/authored client entries follow the same detection for the bridge import. - f463de5: Diagnostics auto-detection now requires the app to declare
@solidjs/diagnosticsin its own package.json (presence in ancestor node_modules surprise-enabled the surface for monorepo fixture apps), and the surface never activates in test mode (vitest browser mode runs a dev serve and was getting the bridge injected into test pages). - cf13314:
serverFunctions.componentsnow also accepts'external': identical totrue, but declares that a composing host (e.g. the Astro adapter or TanStack Start's Solid integration) owns the document wiring — render plugin + client-sideinstallServerComponents()call — itself, so the without-SSR-start-mode warning is skipped instead of printing on every host build. The remaining warning text is also updated: it listed "the bootstrap script" as a required app-side piece, but head bootstrap injection was removed (serialized references self-bootstrap the registry), and it now points hosts atcomponents: 'external'. - e8ffe62: Add experimental
.tsrxcompilation with native and Babel backends, scoped CSS sidecars, HMR and SSR asset integration, and function-level server functions.
@solidjs/vite-plugin@3.0.0-next.37
Patch Changes
- 73751ca: Only attach a request body in the dev middlewares' Node-to-web bridging when the incoming request actually carries one (Content-Length/Transfer-Encoding, or the h2 END_STREAM flag). An unconditionally attached empty stream made bodyless POSTs — zero-argument scripted server function calls, synthetic dispatches — parse as a present-but-unusable body, which @solidjs/web 2.0.0-rc.5 rejects as malformed (400) instead of ignoring.
- 16245b6: Dev middleware recognizes the scripted transport's data address. Scripted server-function calls now go to
<endpoint>/data/<id>(solidjs/solid#3094), and the middleware's module-preload step assumed exactly one path segment after the mount — a cold function only client code references would never be evaluated in the SSR environment for a data-addressed call, answering 404 undervite dev. Dispatch itself was unaffected (mount matching is prefix-based). The id now parses from behind the literaldatasegment too; a function id spelleddatastill parses at the bare address, since an id occupies exactly one segment. - 3a7ff44: Document shell edits now trigger a full page reload instead of being silently absorbed (solidjs/solid#3151). Two sides: the resolved
start.document/src/Document.*module declines HMR in its client compile (it hydrates the wholedocument, so no component swap can ever apply — self-accept + invalidate makes Vite reload instead), and server-environment updates for files with no client-graph counterpart (client-mode documents, authored entry-server, middleware) send a browser full-reload rather than staying suppressed — the suppression exists to protect client HMR from full-reload races, but a server-only file has no client update to race with. - fb9f447: Drop the retired
X-Server-Function-Idheader and?id=addressing fallback from the dev middleware's module-preload path. Addressing is path-only (<endpoint>/<id>and<endpoint>/data/<id>), matching the runtime's removal of its own transitional shims during the RC. - e9b2a39: Read the file hash from the second id segment. Server-function ids are now identity-keyed
<name>-<hash>[-<ordinal>](solidjs/solid#3109) instead of positional<hash>-<ordinal>, so the dev middleware's id-to-module lookup takes the hash fromsplit('-')[1]rather than the first segment.
@solidjs/vite-plugin@3.0.0-next.36
Patch Changes
- 9f0ec40: Dev servers now resolve the
developmentexport condition for externalized server deps. Externalized SSR imports are resolved withresolve.externalConditions(default['node', 'module-sync']), so packages selecting their dev build through thedevelopmentcondition — @solidjs/web's server-functions runtime among them — loaded their production copy undervite dev: thrown server errors reached the client sanitized to "Internal Server Error" instead of carrying the real message, and dev-only diagnostics vanished. The plugin now prependsdevelopmentto each server environment'sexternalConditionswhenever it injects dev mode, matching the treatmentresolve.conditionsalready got.
@solidjs/vite-plugin@3.0.0-next.35
Minor Changes
- 2f0384a: Route path-addressed server function calls (solidjs/solid#3076). A call's address is now
<endpoint>/<id>with arguments in the query, so the dev middleware and the generated request-dispatch gate match the endpoint by mount prefix instead of exact pathname, and the dev middleware's module-preload id comes from the path segment. The retiredX-Server-Function-Idheader and?id=forms remain as transitional fallbacks for the RC window only — they will be dropped before the stable release.
Patch Changes
- 5c50681: Announce the diagnostics surface in the dev-server startup block when
diagnostics: trueis set: two extra lines after Vite's URLs naming the/__solid/diagnosticsendpoint (with its method vocabulary) and the agent skill documents shipped in node_modules. Startup output is the one channel agents reliably read even in projects with no AGENTS.md, making this the discovery path for existing apps and ports. Dev-serve only.