Release RC packages (rc) - #3399
Open
github-actions[bot] wants to merge 1 commit into
Open
github-actions[bot] wants to merge 1 commit into
github-actions[bot] wants to merge 1 commit into
Conversation
github-actions
Bot
force-pushed
the
changeset-release/next
branch
30 times, most recently
from
September 14, 2026 22:46
8d6ea98 to
235acfd
Compare
github-actions
Bot
force-pushed
the
changeset-release/next
branch
from
September 14, 2026 23:01
235acfd to
4ed68ac
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
@solidjs/babel-plugin@2.0.0-rc.9
Patch Changes
8d6de07: Native elements with several spread sources compile to the runtimes' array form instead of a
mergeProps()call, in DOM and SSR output:<div id="x" {...a} {...b}>becomesspread(el, [{ id: "x" }, a, b], …)on the client andssrElement("div", [{ id: "x" }, a, b], …)on the server. The runtimes read the sources directly — later sources win per key, only the winning source is read — with no merge proxy to build and walk, and a reactive spread is a plain thunk called inside the tracking scope, so it mints no memo and consumes no hydration id on either side (the hydratable SSR() => mergeProps(…)wrapper is gone for the same reason). A lone spread still passes straight through (fix(compiler): align lone spread hydration ids #3105). Requires@solidjs/webwith thespread/ssrElementarray forms (feat(web): ssrElement accepts an array of prop sources and a skip predicate #3418, perf(web): spread() creates fewer reactive nodes and accepts a sources array #3419).63560a1:
componentNamesnow applies to SSR output. Under the option both compilers keep thecreateComponentcall they otherwise inline toComp(props)and pass the source tag name —createComponent(Comp, props, "Comp")— so the server runtime's observe/devcreateComponentlabels the owner and a server finding'sownerPathreads<App> › <Page>like the client's. Without the option (prod builds) SSR output is unchanged.@solidjs/vite-pluginalready passes the option for its dev and observe postures, so app server builds pick this up with no config change.Fixes
ssrScopeunder transparent owners: the virtual hole scope swapped the current owner's id counter, but content inside a hole resolves ids by walking past transparent owners, so with one in between (the server-component scope owner; now the labelled component owner) the hole's content took ids from the enclosing counter and disagreed with the client. The scope now swaps the nearest id-bearing owner.1643d2a: The universal renderer's
spread()follows the@solidjs/webcontract (spread()creates three reactive nodes per element #3388):reffolds into the props effect and is re-applied only when its identity changes (refs run with no owner, so nothing they create is disposed by the fold); children keep their own ownedinsert— that effect owns the child subtree — but a plain object whosechildrenis a data property inserts the value with no effect at all. Three reactive nodes become two when children flow through the spread, one when they don't.spreadalso resolves a lone function source inside its own tracking scopes and accepts an array of sources —spread(node, [a, b], skipChildren)— the union of their keys with later sources winning, only the winning source read, function sources called inline with no merge and no memo. Both compilers' universal output uses it: a lone spread passes straight through (reactive included, no moremergeProps(() => …)), and several sources compile to the array instead of amergeProps()call.@solidjs/compiler@2.0.0-rc.9
Patch Changes
8d6de07: Native elements with several spread sources compile to the runtimes' array form instead of a
mergeProps()call, in DOM and SSR output:<div id="x" {...a} {...b}>becomesspread(el, [{ id: "x" }, a, b], …)on the client andssrElement("div", [{ id: "x" }, a, b], …)on the server. The runtimes read the sources directly — later sources win per key, only the winning source is read — with no merge proxy to build and walk, and a reactive spread is a plain thunk called inside the tracking scope, so it mints no memo and consumes no hydration id on either side (the hydratable SSR() => mergeProps(…)wrapper is gone for the same reason). A lone spread still passes straight through (fix(compiler): align lone spread hydration ids #3105). Requires@solidjs/webwith thespread/ssrElementarray forms (feat(web): ssrElement accepts an array of prop sources and a skip predicate #3418, perf(web): spread() creates fewer reactive nodes and accepts a sources array #3419).63560a1:
componentNamesnow applies to SSR output. Under the option both compilers keep thecreateComponentcall they otherwise inline toComp(props)and pass the source tag name —createComponent(Comp, props, "Comp")— so the server runtime's observe/devcreateComponentlabels the owner and a server finding'sownerPathreads<App> › <Page>like the client's. Without the option (prod builds) SSR output is unchanged.@solidjs/vite-pluginalready passes the option for its dev and observe postures, so app server builds pick this up with no config change.Fixes
ssrScopeunder transparent owners: the virtual hole scope swapped the current owner's id counter, but content inside a hole resolves ids by walking past transparent owners, so with one in between (the server-component scope owner; now the labelled component owner) the hole's content took ids from the enclosing counter and disagreed with the client. The scope now swaps the nearest id-bearing owner.1643d2a: The universal renderer's
spread()follows the@solidjs/webcontract (spread()creates three reactive nodes per element #3388):reffolds into the props effect and is re-applied only when its identity changes (refs run with no owner, so nothing they create is disposed by the fold); children keep their own ownedinsert— that effect owns the child subtree — but a plain object whosechildrenis a data property inserts the value with no effect at all. Three reactive nodes become two when children flow through the spread, one when they don't.spreadalso resolves a lone function source inside its own tracking scopes and accepts an array of sources —spread(node, [a, b], skipChildren)— the union of their keys with later sources winning, only the winning source read, function sources called inline with no merge and no memo. Both compilers' universal output uses it: a lone spread passes straight through (reactive included, no moremergeProps(() => …)), and several sources compile to the array instead of amergeProps()call.@solidjs/diagnostics@2.0.0-rc.9
Patch Changes
@solidjs/element@2.0.0-rc.9
Patch Changes
@solidjs/h@2.0.0-rc.9
Patch Changes
c452850:
dynamic()/Dynamicwith a tag-name source honor anxmlnsprop when creating the element (dynamic()/Dynamicwith a string tag: norunHydrationEvents(), namespace ignores parent #3386)The compiler resolves a tag's namespace from its parent at build time; the
dynamic()runtime path creates the element before it has a parent, so a tag that exists in both HTML and SVG (a,script,style,title) was always created as an HTML element —<svg><Link href=…/></svg>withLink = dynamic(() => "a")produced an HTML anchor inside the SVG tree. The instance can now say which one it means with the same attribute compiled JSX uses for the same purpose:<Link xmlns="http://www.w3.org/2000/svg" href=…/>. Likeis,xmlnsis read once, untracked, at creation (the DOM can't re-namespace a node) and then applied as an ordinary attribute, so a client-rendered element carries the same attribute the server serializes. Withoutxmlnsthe namespace is still inferred from the tag name. Hydration is unaffected: it claims the parser-namespaced node.Types:
xmlnsis now accepted on the four tags that exist in both HTML and SVG (a,script,style,title) in addition to the SVG/MathML attribute sets that already had it — those are the only tags where the attribute changes what gets created, and the compiler has honored<a xmlns=…>on them all along. Unambiguous HTML tags (div,span, …) still reject it.dynamic()'s tag-name components inherit it through their intrinsic attribute types. Also syncs@solidjs/h's generated JSX types.Updated dependencies [17b0bda]
Updated dependencies [1af28a1]
Updated dependencies [c452850]
Updated dependencies [5b31076]
Updated dependencies [084e621]
Updated dependencies [da6ed76]
Updated dependencies [5f688a6]
Updated dependencies [c66130d]
Updated dependencies [36db287]
Updated dependencies [7623ce1]
Updated dependencies [af94f67]
Updated dependencies [042b540]
Updated dependencies [7f6332a]
Updated dependencies [40977c9]
@solidjs/html@2.0.0-rc.9
Patch Changes
@solidjs/signals@2.0.0-rc.9
Patch Changes
50323b4: Keep a mainline-computed effect value out of a parked transaction. When an effect stamped by a held transaction recomputed on an unrelated write and no longer read the held source, the forced re-run inside that transaction re-claimed ownership of the value it had just published. A finalize-time re-entry (a
Loadingboundary'sonreset flipping its fallback state) then parked the effect with the transaction, leaving ashow() ? details() : "hidden"reader stale until the unrelated async settled (2.0.0-rc.8 Loading reset leaves an outside conditional reader stale #3412).d7cb456: Fix a second write arriving while an async chain is still in flight (2.0.0-rc Async memo chain publishes a stale result alongside a newer signal value #3373, 2.0.0-rc Repeating a signal write after remounting its async reader publishes it early #3374, 2.0.0-rc Loading reveals stale content before downstream async work finishes #3375, 2.0.0-rc isPending briefly returns false during overlapping updates to an async memo chain #3376).
arestarted whileb's first flight was up),bstays pending ona; the stale landing no longer let the transaction commit the newer signal beside the older derived value (2 / 1, 2.0.0-rc Async memo chain publishes a stale result alongside a newer signal value #3373) or blipisPendingtofalse(2.0.0-rc isPending briefly returns false during overlapping updates to an async memo chain #3376). A fresh flight drops pending entries its inputs propagated earlier — the run read them, so a masked input (an active override, A17) does not hold it._error.source, which a later-pending input overwrites; the held write no longer commits ahead of its answer once the load is re-asked under anon-scoped boundary (2.0.0-rc Loading reveals stale content before downstream async work finishes #3375).Loadingboundary records every source the notifying effect is pending on, not only the one the notification carries — anonreset no longer reveals content when the boundary's one collected source settles while the effect is still pending on a flight it already carried (2.0.0-rc Loading reveals stale content before downstream async work finishes #3375).Count: 1besideDetails: 0, 2.0.0-rc Repeating a signal write after remounting its async reader publishes it early #3374).Loadingboundary'sonreset ends the hold on writes that only its readers observed: a reader registered while the boundary showed content stops blocking once the boundary flips to its fallback, and the parked transaction is woken and re-judged in the same drain. A reader outside the boundary that also observes the flight still holds it (2.0.0-rc Loading reveals stale content before downstream async work finishes #3375).a0d6dd2: Conditional JSX across a held branch change stays coherent with its inputs (Conditional JSX stays stale during a held branch change #3438).
An effect's dependencies are the committed frame's until its run applies — the effect twin of the memo rule from 2.0.0-rc.8 conditional memo disagrees with its inputs during a held branch change #3410. A pass that direct-committed its value but whose run was stashed with a hold (the same flush pended an async memo) used to trim the dependencies its previous run still displayed, so a later mainline write to one of them never reached the effect:
{show() ? count() : "hidden"}held onshow → falseshowedCount: 1besidePanel: 0whileShowstill readtrue. The trim now waits for the run to apply, so the write re-derives the effect against the committed inputs (Panel: 1besideCount: 1), and the hold's landing revealshiddenwithShow: false.c3ae310: Conditional memos across a held branch change stay coherent with their inputs (2.0.0-rc.8 conditional memo reveals a held signal value early #3408, 2.0.0-rc.8 conditional memo disagrees with its inputs during a held branch change #3410).
Panel: 1next toCount: 0). The read entry is the twin of the existing write-side (setSignal) and stamped-recompute entries.Count: 1next toSelected: 0whileFixed: false).6095955: Two ways a held write stayed staged past the point it should have revealed:
<Show>unmounting the reader of a pending memo) was never re-judged — the flush only evaluates the active transaction, and nothing re-entered a parked one. The writes held with that reader (a signal set while it was pending) stayed staged forever. Disposing a pending reader parked in a transaction now wakes it; the flush re-enters a woken transaction on an otherwise idle pass, prunes the dead reporter and commits (2.0.0-rc Signal update lost when a suspending reader is unmounted #3372).latest()(or otherwise adopts an optimistic lane through its deps) direct-commits on a lane pass, but a hold it had staged on an earlier, lane-free pass of the same transaction was left in place and the transaction's commit published that older frame over the fresh value —Pair: 0 / 0for good. A lane recompute now drops the hold it supersedes, override or not (2.0.0-rc.8 latest stops updating after intermediate write restores the initial value #3377).e80f241: A node recomputed under a held transaction no longer tears down the committed frame's children immediately. Status propagation stamps a parked dependent with the transaction without recomputing it, so its owned children (nested render effects, memos,
onCleanupregistrations) still belong to what is on screen; when the pending source landed, the recompute disposed them on the spot and their cleanups ran mid-hold, before the transaction's atomic reveal (2.0.0-rc nested render effect cleanup run too early #3404). Those children are now deferred as zombies until the node commits, matching the plain-flush path. Children built by a recompute that never committed (a staged value, a pending window, a run under the transaction) are still disposed immediately on the next re-run — no frame ever showed them.14ded24:
merge()no longer flattens through a plain-object merge result. That form is a real object callers may copy (Reflect.ownKeysdescriptor copies,{...props}) or mutate afterwards (@solidjs/htmlassigns props and achildrengetter after spreading), and a latermergetunnelled back to the original sources through the$SOURCESsymbol — resurrecting removed keys and dropping added or overwritten ones (merge()re-flattens through a stale$SOURCESand discards the object's own properties #3384). The plain result now records no sources and is read like any other object; only merge proxies, whose writes are no-ops, are still flattened. A side effect:merge(defaults, props)wherepropsis itself a plain merge result that covers every default now returnspropsdirectly instead of always allocating.25c5064: Optimistic frames release when nothing authoritative is left to wait on, and a shared render effect no longer entangles unrelated updates (2.0.0-rc.8 optimistic update keeps waiting after its last async reader unmounts #3426, 2.0.0-rc.8 optimistic update first reveals after its action body has finished #3427, 2.0.0-rc.8 shared render effect entangles a synchronous action with an unrelated pending update #3407).
sourceObserved, shared with the settle verdict), instead of waiting for a flight nobody observes to land.actionwrite to a signal that shared a hole with a held async ({b()}:{detailsA()}) merged into the async's transaction and waited (0:0 → 2:1) while the same plain write passed through.recomputere-enters a stamped node's transaction for memos only; the landing of a flight enters every transaction waiting on it (enterWaiting), which is where the effect re-entry's one legitimate job — completing the reveals that discovered the flight — now lives. Two independent flights read in one hole land at their own times.1ce0f85: Optimistic settle verdicts (2.0.0-rc.8 combined isPending reports false while both individual reads report true during an optimistic update #3409, 2.0.0-rc.8 onSettled reads inconsistent values during an optimistic revert #3411).
isPending(() => [a(), b()])over two async siblings of an optimistic value reports pending as soon as either read is (2.0.0-rc.8 combined isPending reports false while both individual reads report true during an optimistic update #3409).assignOrMergeLanenow follows a merged lane to its root and runs the parent/child check on it; the old "merged lane is stale, take the source lane" shortcut moved the combined probe's effect onto the held parent lane, where its verdict waited on the async it reports.onSettledcallback (event handler, action body) reads a settled world (2.0.0-rc.8 onSettled reads inconsistent values during an optimistic revert #3411). The settle only enqueues the reverted subscribers for the next pass, so a callback fired in the commit pass saw the optimistic source reverted beside a sync memo of it still holding the optimistic value. The fire now waits for the heap to drain.9da7f0a:
isPending(details)reports the load of an optimistic value whendetailsderives it through an async memo (2.0.0-rc.8 isPending stays false while an async memo loads an optimistic value #3379).notifyStatusnow assigns the node's optimistic lane before poking its companions, so a companion lane created by the poke is parented to the node's lane: the indicator effect flushes on the companion's child lane immediately instead of merging it into the held lane and waiting on the async it reports.76230f9: Report
isPending()true for a synchronous memo held by a downstream async memo. The memo's staged recompute only refreshed its verdict companion inside an active transition, but a plain flush can become a hold after that recompute (an async memo pends and the batch is adopted into a transaction), so the memo read not-pending while the signal it derived from read pending (2.0.0-rc.8 isPending reports false for a held synchronous memo #3413).632e45c: Give the latest() shadow companion the
ownedWriteflag its isPending companion already carries (2.0.0-rc.8 latest read throws an owned-scope write error when an action resumes #3378). A companion sync is internal plumbing that can run from inside a computation — a transition-held memo recompute pulled mid-tick by a reader creating or refreshing its latest() shadow — and the dev owned-scope write guard halted the app on the shadow write. Toggling a JSX branch that readslatest(memo)off while an action is pending and restoring it as the action resumes threw REACTIVE_WRITE_IN_OWNED_SCOPE.ca05917:
OBSERVE.excludenow covers writes: a root write to an excluded subject (the observer's own store or signal) no longer counts toward the interaction that made it, and an interaction whose writes all went to excluded subjects with none of the app's work run — a click on a devtools panel's own button — is not recorded. Store nodes carry the owner their store was created under, so an excluded panel's store is an excluded subject like its signals.7623ce1: Server diagnostics on
OBSERVE.diagnostics;OBSERVE.serverowned bysolid-js's server entryThe server runtime now reports on the same structured channel as the client. Findings — facts about a render, present in observe and dev builds —
SSR_RENDER_ERROR_CONTAINED(a render error a boundary routed;data.handlingisfallback,client, orfailed— the structured face of whatrenderToStream'sonErrorreceives, on the process-wide channel),SSR_SUBTREE_ABANDONED(a failed fragment's pending descendants discarded),SSR_STREAM_ABANDONED(consumer cancelled or sink failed mid-render),LATE_HEADER_WRITE(recorded beside the existing dev throw / prod log),SERVER_FN_ERROR_SANITIZED(the original error the production wire replaced), andFRAME_MARKER_CORRUPTEDfrom the frames client. Checks — dev-only guidance — convert every serverconsole.warnto a code:SERVER_WRITE,REVEAL_IN_RENDER_TO_STRING,LAZY_ASSET_UNMAPPED,PRELOAD_DESCRIPTOR_INVALID,HEAD_TAG_INVALID,BEHAVIOR_CLAIM_DROPPED, andUNRECOGNIZED_INSERT_VALUE(now one code and arenderkind on both platforms);ASYNC_OUTSIDE_LOADING_BOUNDARYon the server records withdata.side: "server"before it throws. Server components are labelled forownerPath(createComponentruns the body under a transparent<Name>owner in observe/dev — no hydration id consumed), soin <App> › <Page>reads the same on both sides, and the server entry installs the same repair-guide console footer as the client. Prod artifacts carry none of it;DiagnosticKindgainsssr,head,render.OBSERVE.server's objects (the invocation listener set, the trace-provider slot) are now created bysolid-js's server entry, once per process underSymbol.for("solid-js/observe/server")onglobalThis, rather than by@solidjs/web's module init: an observer'sinit()that imports onlysolid-jscan subscribe andprovidebefore the web runtime has loaded, and a host that bundles the runtime into its server build and instruments through a--imported module finds one listener set and one provider across both copies. The core keepsserver: {}; the client pays nothing.af94f67: Server observe surface:
OBSERVE.serverand the invocation channelOBSERVEgains aserverslot — an augmentableServerObserveinterface declared empty in@solidjs/signals(re-exported bysolid-js), typed and emitted into by@solidjs/web's server runtime, so server-side observability consumers subscribe on the oneOBSERVEobject they already know from the client. The first channel isOBSERVE.server.invocations:subscribe("invocation", (event, live) => …)delivers one{ id, direct, at, durationMs, outcome, deferred? }record per server-function execution — HTTP dispatch and direct SSR calls alike — when it settles, with the request event,request,args, and the result or the error as thrown beside it. Observers, not policy: any number of listeners, none able to alter the call;wrapInvocationremains the single policy hook.@solidjs/webnow publishes observe-tier server artifacts (dist/server.observe.js,server-functions/dist/server.observe.js,frames/dist/server.observe.js) under theobserveexport condition, alongside the existing dev/prod pairs. The surface and every emit site fold out of the prod artifacts.solid-js@2.0.0-rc.9
Patch Changes
7623ce1: Server diagnostics on
OBSERVE.diagnostics;OBSERVE.serverowned bysolid-js's server entryThe server runtime now reports on the same structured channel as the client. Findings — facts about a render, present in observe and dev builds —
SSR_RENDER_ERROR_CONTAINED(a render error a boundary routed;data.handlingisfallback,client, orfailed— the structured face of whatrenderToStream'sonErrorreceives, on the process-wide channel),SSR_SUBTREE_ABANDONED(a failed fragment's pending descendants discarded),SSR_STREAM_ABANDONED(consumer cancelled or sink failed mid-render),LATE_HEADER_WRITE(recorded beside the existing dev throw / prod log),SERVER_FN_ERROR_SANITIZED(the original error the production wire replaced), andFRAME_MARKER_CORRUPTEDfrom the frames client. Checks — dev-only guidance — convert every serverconsole.warnto a code:SERVER_WRITE,REVEAL_IN_RENDER_TO_STRING,LAZY_ASSET_UNMAPPED,PRELOAD_DESCRIPTOR_INVALID,HEAD_TAG_INVALID,BEHAVIOR_CLAIM_DROPPED, andUNRECOGNIZED_INSERT_VALUE(now one code and arenderkind on both platforms);ASYNC_OUTSIDE_LOADING_BOUNDARYon the server records withdata.side: "server"before it throws. Server components are labelled forownerPath(createComponentruns the body under a transparent<Name>owner in observe/dev — no hydration id consumed), soin <App> › <Page>reads the same on both sides, and the server entry installs the same repair-guide console footer as the client. Prod artifacts carry none of it;DiagnosticKindgainsssr,head,render.OBSERVE.server's objects (the invocation listener set, the trace-provider slot) are now created bysolid-js's server entry, once per process underSymbol.for("solid-js/observe/server")onglobalThis, rather than by@solidjs/web's module init: an observer'sinit()that imports onlysolid-jscan subscribe andprovidebefore the web runtime has loaded, and a host that bundles the runtime into its server build and instruments through a--imported module finds one listener set and one provider across both copies. The core keepsserver: {}; the client pays nothing.af94f67: Server observe surface:
OBSERVE.serverand the invocation channelOBSERVEgains aserverslot — an augmentableServerObserveinterface declared empty in@solidjs/signals(re-exported bysolid-js), typed and emitted into by@solidjs/web's server runtime, so server-side observability consumers subscribe on the oneOBSERVEobject they already know from the client. The first channel isOBSERVE.server.invocations:subscribe("invocation", (event, live) => …)delivers one{ id, direct, at, durationMs, outcome, deferred? }record per server-function execution — HTTP dispatch and direct SSR calls alike — when it settles, with the request event,request,args, and the result or the error as thrown beside it. Observers, not policy: any number of listeners, none able to alter the call;wrapInvocationremains the single policy hook.@solidjs/webnow publishes observe-tier server artifacts (dist/server.observe.js,server-functions/dist/server.observe.js,frames/dist/server.observe.js) under theobserveexport condition, alongside the existing dev/prod pairs. The surface and every emit site fold out of the prod artifacts.5426ffb: Fix a server-rendered
<Errored>fallback hydrating dead ([2.0.0-rc.8] Errored fallback rendered during initial SSR has an unresponsive counter #3414). When the boundary's children threw synchronously on the server and the fallback was a zero-arg thunk (fallback={() => <Fallback />}), the thunk was handed back unresolved and unwrapped by the enclosing boundary — the client does that inside the boundary's flatten computed, the server did it inline under the boundary owner — so the fallback's hydration keys disagreed, its root element failed to claim the server node, and its event handlers and effects never attached. The serverErroredandLoadingboundaries now resolve their children's result in a virtual id scope mirroring the client's second computed; the error record stays at the boundary id.63560a1:
componentNamesnow applies to SSR output. Under the option both compilers keep thecreateComponentcall they otherwise inline toComp(props)and pass the source tag name —createComponent(Comp, props, "Comp")— so the server runtime's observe/devcreateComponentlabels the owner and a server finding'sownerPathreads<App> › <Page>like the client's. Without the option (prod builds) SSR output is unchanged.@solidjs/vite-pluginalready passes the option for its dev and observe postures, so app server builds pick this up with no config change.Fixes
ssrScopeunder transparent owners: the virtual hole scope swapped the current owner's id counter, but content inside a hole resolves ids by walking past transparent owners, so with one in between (the server-component scope owner; now the labelled component owner) the hole's content took ids from the enclosing counter and disagreed with the client. The scope now swaps the nearest id-bearing owner.Updated dependencies [50323b4]
Updated dependencies [d7cb456]
Updated dependencies [a0d6dd2]
Updated dependencies [c3ae310]
Updated dependencies [6095955]
Updated dependencies [e80f241]
Updated dependencies [14ded24]
Updated dependencies [25c5064]
Updated dependencies [1ce0f85]
Updated dependencies [9da7f0a]
Updated dependencies [76230f9]
Updated dependencies [632e45c]
Updated dependencies [ca05917]
Updated dependencies [7623ce1]
Updated dependencies [af94f67]
@solidjs/universal@2.0.0-rc.9
Patch Changes
spread()follows the@solidjs/webcontract (spread()creates three reactive nodes per element #3388):reffolds into the props effect and is re-applied only when its identity changes (refs run with no owner, so nothing they create is disposed by the fold); children keep their own ownedinsert— that effect owns the child subtree — but a plain object whosechildrenis a data property inserts the value with no effect at all. Three reactive nodes become two when children flow through the spread, one when they don't.spreadalso resolves a lone function source inside its own tracking scopes and accepts an array of sources —spread(node, [a, b], skipChildren)— the union of their keys with later sources winning, only the winning source read, function sources called inline with no merge and no memo. Both compilers' universal output uses it: a lone spread passes straight through (reactive included, no moremergeProps(() => …)), and several sources compile to the array instead of amergeProps()call.@solidjs/web@2.0.0-rc.9
Patch Changes
17b0bda: Deprecate
<Dynamic>in favor ofdynamic()<Dynamic component={…}>is the same primitive asdynamic()with a worse shape: the tag travels in the props bag, so every instance mergescomponentin at the call site,omits it back out inside, and builds a freshdynamic()factory (with its memo) because a JSX wrapper has nowhere to hoist one. Polymorphic-component libraries end up omittingas, handing the tag to<Dynamic>, which merges it back in undercomponentto omit it again.dynamic()has none of that for one extra line:DynamicandDynamicPropsare marked@deprecated(editors strike them through; no runtime warning). They remain available in 2.0 — the RCs are past public API removals — but new code should usedynamic(). The migration guide and control-flow RFC are updated accordingly.1af28a1: Call
runHydrationEvents()afterdynamic()spreads props onto a string tag, so events the hydration script queued for a<Dynamic>element are replayed instead of being dropped until_$HY.done.c452850:
dynamic()/Dynamicwith a tag-name source honor anxmlnsprop when creating the element (dynamic()/Dynamicwith a string tag: norunHydrationEvents(), namespace ignores parent #3386)The compiler resolves a tag's namespace from its parent at build time; the
dynamic()runtime path creates the element before it has a parent, so a tag that exists in both HTML and SVG (a,script,style,title) was always created as an HTML element —<svg><Link href=…/></svg>withLink = dynamic(() => "a")produced an HTML anchor inside the SVG tree. The instance can now say which one it means with the same attribute compiled JSX uses for the same purpose:<Link xmlns="http://www.w3.org/2000/svg" href=…/>. Likeis,xmlnsis read once, untracked, at creation (the DOM can't re-namespace a node) and then applied as an ordinary attribute, so a client-rendered element carries the same attribute the server serializes. Withoutxmlnsthe namespace is still inferred from the tag name. Hydration is unaffected: it claims the parser-namespaced node.Types:
xmlnsis now accepted on the four tags that exist in both HTML and SVG (a,script,style,title) in addition to the SVG/MathML attribute sets that already had it — those are the only tags where the attribute changes what gets created, and the compiler has honored<a xmlns=…>on them all along. Unambiguous HTML tags (div,span, …) still reject it.dynamic()'s tag-name components inherit it through their intrinsic attribute types. Also syncs@solidjs/h's generated JSX types.5b31076:
ssrElementno longer emitsstyle=""/class=""for nullish values; they are omitted like every other attribute, matching the client. Skipped props also leave no stray whitespace in the opening tag (ssrElementemitsstyle=""/class=""for nullish values #3382).084e621: SSR emits the
<!--!$-->text separator only between items that resolve to text. Adjacent memos and components that yield elements — everyDynamic,Show, or wrapper-library instance in a list — no longer carry a separator and a comment node each. Text that becomes adjacent through a nested array or a dropped nullish item is now separated, so it hydrates into distinct text nodes (SSR emits<!--!$-->between adjacent memo/function items even when they resolve to elements #3383).da6ed76: SSR: fold the text-separator entry wrappers back into
tryResolveStringandresolveSSRNode. fix(web): decide SSR text separators on resolved values #3394 split each walker into an entry function that reset the separator state and a recursive body; the bodies are too large for V8 to inline, so every template hole paid an extra call. The recursion now passes anestedflag instead — one function each, same output — recovering the ~2% SSR throughput that split cost on element-heavy pages (up to 7% where holes are dense).5f688a6: GET-encoded server function calls no longer send the
X-Server-Function-Instanceheader (The server functions implementation adds aX-Server-Function-Instanceheader, making it impossible to preload them reliably across browsers with<link>tags #3406). A<link rel="preload" as="fetch">is reused only by a fetch that matches it exactly, headers included, so the per-call header made browsers fetch every preloaded read twice. A read's identity is now its url alone; the instance id keeps riding the POST transport, where it still names the call to the handler's hooks ascontext.instance(null for reads, as for no-JS callers).prepareRequestvalidation adapts: on a GET call the method the transport set stands sentinel for a returned init that dropped the original.c66130d: Remove the
X-Server-Function-Instanceheader from the server-function wire protocol. Since A cacheable raw Response is shaped by a request header nothing keys on #3094 it decided nothing: the answer shape is the address (/data/for the scripted transport, bare for plain HTTP), no-JS gating is the address plus a form post, and fix(web): send GET server function calls without the instance header (#3406) #3416 had already dropped it from GET reads so preloads could match. What remained was a per-call id the handler copied intocontext.instancefortransformResult/transformFlightResult, which nothing consumed; cross-wire correlation is the trace context's job (traceparent, feat(web): trace context — getTraceContext(), W3C traceparent, OBSERVE.server.trace (C1) #3402). Breaking, nominally:context.instanceis gone from the hook contexts, andINSTANCE_HEADERis no longer exported from@solidjs/web/server-functions/{client,server}. A legacy header on an incoming request is ignored, as it already was. TheprepareRequestvalidation (prepareRequest's return replaces the init unvalidated, so a hook that returns a fresh object drops the payload, the signal and the protocol headers #3174) now uses the transport's method as its sentinel on every call shape, so a hook returning a fresh{ headers }instead of spreading is still refused before dispatch. Client-sideobserveServerFunctionCallsevents keep their localinstanceid for pairing a request with its response.36db287:
renderToStringnow disposes its reactive root synchronously before returning instead of viasetTimeout, so a synchronous loop of renders no longer retains every graph until the next macrotask (renderToStringdisposes its root viasetTimeout; graphs are retained across synchronous renders #3385). The request event's response head is committed right before that dispose — the same head-freeze point an awaitedrenderToStreamalready uses — sohttpStatus/httpHeaderdeclarations still reachcreateSSRResponse. A render that throws leaves the head uncommitted and retracts its declarations as before.7623ce1: Server diagnostics on
OBSERVE.diagnostics;OBSERVE.serverowned bysolid-js's server entryThe server runtime now reports on the same structured channel as the client. Findings — facts about a render, present in observe and dev builds —
SSR_RENDER_ERROR_CONTAINED(a render error a boundary routed;data.handlingisfallback,client, orfailed— the structured face of whatrenderToStream'sonErrorreceives, on the process-wide channel),SSR_SUBTREE_ABANDONED(a failed fragment's pending descendants discarded),SSR_STREAM_ABANDONED(consumer cancelled or sink failed mid-render),LATE_HEADER_WRITE(recorded beside the existing dev throw / prod log),SERVER_FN_ERROR_SANITIZED(the original error the production wire replaced), andFRAME_MARKER_CORRUPTEDfrom the frames client. Checks — dev-only guidance — convert every serverconsole.warnto a code:SERVER_WRITE,REVEAL_IN_RENDER_TO_STRING,LAZY_ASSET_UNMAPPED,PRELOAD_DESCRIPTOR_INVALID,HEAD_TAG_INVALID,BEHAVIOR_CLAIM_DROPPED, andUNRECOGNIZED_INSERT_VALUE(now one code and arenderkind on both platforms);ASYNC_OUTSIDE_LOADING_BOUNDARYon the server records withdata.side: "server"before it throws. Server components are labelled forownerPath(createComponentruns the body under a transparent<Name>owner in observe/dev — no hydration id consumed), soin <App> › <Page>reads the same on both sides, and the server entry installs the same repair-guide console footer as the client. Prod artifacts carry none of it;DiagnosticKindgainsssr,head,render.OBSERVE.server's objects (the invocation listener set, the trace-provider slot) are now created bysolid-js's server entry, once per process underSymbol.for("solid-js/observe/server")onglobalThis, rather than by@solidjs/web's module init: an observer'sinit()that imports onlysolid-jscan subscribe andprovidebefore the web runtime has loaded, and a host that bundles the runtime into its server build and instruments through a--imported module finds one listener set and one provider across both copies. The core keepsserver: {}; the client pays nothing.af94f67: Server observe surface:
OBSERVE.serverand the invocation channelOBSERVEgains aserverslot — an augmentableServerObserveinterface declared empty in@solidjs/signals(re-exported bysolid-js), typed and emitted into by@solidjs/web's server runtime, so server-side observability consumers subscribe on the oneOBSERVEobject they already know from the client. The first channel isOBSERVE.server.invocations:subscribe("invocation", (event, live) => …)delivers one{ id, direct, at, durationMs, outcome, deferred? }record per server-function execution — HTTP dispatch and direct SSR calls alike — when it settles, with the request event,request,args, and the result or the error as thrown beside it. Observers, not policy: any number of listeners, none able to alter the call;wrapInvocationremains the single policy hook.@solidjs/webnow publishes observe-tier server artifacts (dist/server.observe.js,server-functions/dist/server.observe.js,frames/dist/server.observe.js) under theobserveexport condition, alongside the existing dev/prod pairs. The surface and every emit site fold out of the prod artifacts.042b540: Trace context:
getTraceContext(), W3Ctraceparenton the exchange, and theOBSERVE.server.traceprovider slotThe server runtime now reads the W3C Trace Context half of the HTTP exchange once per request — continuing an incoming
traceparent(withtracestate/baggagebeside it) or originating a trace when none came in — and exposes it throughgetTraceContext()from@solidjs/web:{ traceId, spanId, parentId?, sampled?, state?, baggage?, entries }, one object per request (direct SSR-time server-function calls included), the render's own for a render outside a request scope,undefinedoutside both and on the client. Application code forwards a trace downstream withentries.traceparent. This is core HTTP behavior in every build tier.The trace is also handed down to the browser:
entriesare emitted asServer-Timingmetrics (traceparent;desc="00-…") when the response head commits —createSSRResponse,commitEventResponse(now also for an event without a response stub, such as the server-function handler's default event), andcommitResponseStub(which accepts the owningeventin its options) — and as<meta name="…" content="…">tags in the HTML shell head, delivered wherever the head content goes (</head>splice,onHead). AServer-Timingname the application already wrote is respected;Server-Timingnow folds entry by entry when a stub and a response/responseInitboth carry one. The browser is told only when something is recording the trace — the incomingtraceparentwas sampled, or a provider answered — never for a trace the runtime originated alone or an unsampled upstream one (what load balancers and meshes stamp on every request), so an app with no APM sees zero wire change.In observe/dev builds,
OBSERVE.server.trace.provide(provider)installs a single global provider whose answer merges over the derivation (fields replace,entriesmerge by name) — how an APM's server SDK contributes its active span and vendor entries (sentry-trace/baggage) once, with no per-request entry point into the host.7f6332a:
spread()creates fewer reactive nodes per element (spread()creates three reactive nodes per element #3388):reffolds into the attribute effect and is re-applied only when its identity changes (refs run with no owner, so nothing they create is disposed by the fold); children keep their own ownedinsert— that effect owns the child subtree, and merging it would rebuild the children on every attribute change — but a plain object whosechildrenis a data property inserts the value with no effect at all. Three nodes become two when children flow through the spread, one when they don't.spreadalso accepts an array of sources with an optionalskippredicate —spread(el, [a, b], skipChildren, skip)— the union of own keys with later sources winning, only the winning source read, function sources called inline with no memo (and so no hydration id), matching the serverssrElementarray form.40977c9:
ssrElementaccepts an array of prop sources and an optionalskippredicate:ssrElement(tag, [a, b, c], children, needsId, skip). The array form serializes straight from the sources with the exact output ofssrElement(tag, merge(a, b, c), ...)— later sources win per key, attributes land in merged order, only the winning source's getter is read (once), andskip(key)drops a key from every source without reading it — so libraries and compilers spreading several sources no longer have to build an intermediate merged object that is walked once and discarded. The array (or a thunk yielding it) is resolved after the hydration key is taken; a function source is a plain thunk called once that creates no memo and consumes no hydration ids. Nullish sources are empty. The single-object form is unchanged.Updated dependencies [7623ce1]
Updated dependencies [af94f67]
Updated dependencies [5426ffb]
Updated dependencies [63560a1]
test-integration@2.0.0-rc.9
Patch Changes