Skip to content

Release RC packages (rc) - #3399

Open
github-actions[bot] wants to merge 1 commit into
nextfrom
changeset-release/next
Open

github-actions[bot] wants to merge 1 commit into
nextfrom
changeset-release/next

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 12, 2026

Copy link
Copy Markdown

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.

⚠️⚠️⚠️⚠️⚠️⚠️

next is currently in pre mode so this branch has prereleases rather than normal releases. If you want to exit prereleases, run changeset pre exit on next.

⚠️⚠️⚠️⚠️⚠️⚠️

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}> becomes spread(el, [{ id: "x" }, a, b], …) on the client and ssrElement("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/web with the spread/ssrElement array 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: componentNames now applies to SSR output. Under the option both compilers keep the createComponent call they otherwise inline to Comp(props) and pass the source tag name — createComponent(Comp, props, "Comp") — so the server runtime's observe/dev createComponent labels the owner and a server finding's ownerPath reads <App> › <Page> like the client's. Without the option (prod builds) SSR output is unchanged. @solidjs/vite-plugin already passes the option for its dev and observe postures, so app server builds pick this up with no config change.

    Fixes ssrScope under 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/web contract (spread() creates three reactive nodes per element #3388): ref folds 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 owned insert — that effect owns the child subtree — but a plain object whose children is 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. spread also 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 more mergeProps(() => …)), and several sources compile to the array instead of a mergeProps() 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}> becomes spread(el, [{ id: "x" }, a, b], …) on the client and ssrElement("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/web with the spread/ssrElement array 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: componentNames now applies to SSR output. Under the option both compilers keep the createComponent call they otherwise inline to Comp(props) and pass the source tag name — createComponent(Comp, props, "Comp") — so the server runtime's observe/dev createComponent labels the owner and a server finding's ownerPath reads <App> › <Page> like the client's. Without the option (prod builds) SSR output is unchanged. @solidjs/vite-plugin already passes the option for its dev and observe postures, so app server builds pick this up with no config change.

    Fixes ssrScope under 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/web contract (spread() creates three reactive nodes per element #3388): ref folds 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 owned insert — that effect owns the child subtree — but a plain object whose children is 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. spread also 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 more mergeProps(() => …)), and several sources compile to the array instead of a mergeProps() 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() / Dynamic with a tag-name source honor an xmlns prop when creating the element (dynamic() / Dynamic with a string tag: no runHydrationEvents(), 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> with Link = 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=…/>. Like is, xmlns is 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. Without xmlns the namespace is still inferred from the tag name. Hydration is unaffected: it claims the parser-namespaced node.

    Types: xmlns is 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/web@2.0.0-rc.9

@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 Loading boundary's on reset flipping its fallback state) then parked the effect with the transaction, leaving a show() ? 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).

  • 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 on show → false showed Count: 1 beside Panel: 0 while Show still read true. The trim now waits for the run to apply, so the write re-derives the effect against the committed inputs (Panel: 1 beside Count: 1), and the hold's landing reveals hidden with Show: 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).

    • A tracked computation served a live transaction's staged value now enters that transaction, so its result is held with it (2.0.0-rc.8 conditional memo reveals a held signal value early #3408). A memo whose branch flipped mainline and started reading a held signal previously published the staged value beside the signal's committed one (Panel: 1 next to Count: 0). The read entry is the twin of the existing write-side (setSignal) and stamped-recompute entries.
    • A memo's dependencies are the committed frame's until the frame is replaced (2.0.0-rc.8 conditional memo disagrees with its inputs during a held branch change #3410): a pass that stages its value leaves the previous pass's dependency tail linked, and the commit trims it. A write to a dependency the committed value still derives from reaches the memo and joins its hold — as an unconditional read would — instead of revealing the write beside a stale committed derivation (Count: 1 next to Selected: 0 while Fixed: false).
  • 6095955: Two ways a held write stayed staged past the point it should have revealed:

    • A transaction whose only reporter is disposed by ambient work (a <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).
    • An effect that reads 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 / 0 for 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, onCleanup registrations) 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.ownKeys descriptor copies, {...props}) or mutate afterwards (@solidjs/html assigns props and a children getter after spreading), and a later merge tunnelled back to the original sources through the $SOURCES symbol — resurrecting removed keys and dropping added or overwritten ones (merge() re-flattens through a stale $SOURCES and 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) where props is itself a plain merge result that covers every default now returns props directly 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).

    • The last async reader of an optimistic value unmounting mid-action releases the frame (2.0.0-rc.8 optimistic update keeps waiting after its last async reader unmounts #3426): the lane's hold check prunes dead reporters itself (sourceObserved, shared with the settle verdict), instead of waiting for a flight nobody observes to land.
    • The action body ending starts the correction (2.0.0-rc.8 optimistic update first reveals after its action body has finished #3427): with the bodies over and no authoritative work in flight — no override node's own source, no held plain load the action asked for — each override's truth supersedes it now and the graph re-derives from it as the transaction's held work, settling when that lands. Before, the settle waited for the obsolete lane-derived flight, flashed the obsolete optimistic frame, then reverted and re-asked. A co-written flag stays through a plain load; optimistic store edits keep the settle-then-revert order.
    • A render effect's pass belongs to whatever dirtied it (2.0.0-rc.8 shared render effect entangles a synchronous action with an unrelated pending update #3407): a sync action write 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. recompute re-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).

  • 9da7f0a: isPending(details) reports the load of an optimistic value when details derives it through an async memo (2.0.0-rc.8 isPending stays false while an async memo loads an optimistic value #3379). notifyStatus now 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 ownedWrite flag 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 reads latest(memo) off while an action is pending and restoring it as the action resumes threw REACTIVE_WRITE_IN_OWNED_SCOPE.

  • ca05917: OBSERVE.exclude now 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.server owned by solid-js's server entry

    The 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.handling is fallback, client, or failed — the structured face of what renderToStream's onError receives, 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), and FRAME_MARKER_CORRUPTED from the frames client. Checks — dev-only guidance — convert every server console.warn to a code: SERVER_WRITE, REVEAL_IN_RENDER_TO_STRING, LAZY_ASSET_UNMAPPED, PRELOAD_DESCRIPTOR_INVALID, HEAD_TAG_INVALID, BEHAVIOR_CLAIM_DROPPED, and UNRECOGNIZED_INSERT_VALUE (now one code and a render kind on both platforms); ASYNC_OUTSIDE_LOADING_BOUNDARY on the server records with data.side: "server" before it throws. Server components are labelled for ownerPath (createComponent runs the body under a transparent <Name> owner in observe/dev — no hydration id consumed), so in <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; DiagnosticKind gains ssr, head, render.

    OBSERVE.server's objects (the invocation listener set, the trace-provider slot) are now created by solid-js's server entry, once per process under Symbol.for("solid-js/observe/server") on globalThis, rather than by @solidjs/web's module init: an observer's init() that imports only solid-js can subscribe and provide before 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 keeps server: {}; the client pays nothing.

  • af94f67: Server observe surface: OBSERVE.server and the invocation channel

    OBSERVE gains a server slot — an augmentable ServerObserve interface declared empty in @solidjs/signals (re-exported by solid-js), typed and emitted into by @solidjs/web's server runtime, so server-side observability consumers subscribe on the one OBSERVE object they already know from the client. The first channel is OBSERVE.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; wrapInvocation remains the single policy hook.

    @solidjs/web now publishes observe-tier server artifacts (dist/server.observe.js, server-functions/dist/server.observe.js, frames/dist/server.observe.js) under the observe export 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.server owned by solid-js's server entry

    The 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.handling is fallback, client, or failed — the structured face of what renderToStream's onError receives, 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), and FRAME_MARKER_CORRUPTED from the frames client. Checks — dev-only guidance — convert every server console.warn to a code: SERVER_WRITE, REVEAL_IN_RENDER_TO_STRING, LAZY_ASSET_UNMAPPED, PRELOAD_DESCRIPTOR_INVALID, HEAD_TAG_INVALID, BEHAVIOR_CLAIM_DROPPED, and UNRECOGNIZED_INSERT_VALUE (now one code and a render kind on both platforms); ASYNC_OUTSIDE_LOADING_BOUNDARY on the server records with data.side: "server" before it throws. Server components are labelled for ownerPath (createComponent runs the body under a transparent <Name> owner in observe/dev — no hydration id consumed), so in <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; DiagnosticKind gains ssr, head, render.

    OBSERVE.server's objects (the invocation listener set, the trace-provider slot) are now created by solid-js's server entry, once per process under Symbol.for("solid-js/observe/server") on globalThis, rather than by @solidjs/web's module init: an observer's init() that imports only solid-js can subscribe and provide before 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 keeps server: {}; the client pays nothing.

  • af94f67: Server observe surface: OBSERVE.server and the invocation channel

    OBSERVE gains a server slot — an augmentable ServerObserve interface declared empty in @solidjs/signals (re-exported by solid-js), typed and emitted into by @solidjs/web's server runtime, so server-side observability consumers subscribe on the one OBSERVE object they already know from the client. The first channel is OBSERVE.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; wrapInvocation remains the single policy hook.

    @solidjs/web now publishes observe-tier server artifacts (dist/server.observe.js, server-functions/dist/server.observe.js, frames/dist/server.observe.js) under the observe export 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 server Errored and Loading boundaries 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: componentNames now applies to SSR output. Under the option both compilers keep the createComponent call they otherwise inline to Comp(props) and pass the source tag name — createComponent(Comp, props, "Comp") — so the server runtime's observe/dev createComponent labels the owner and a server finding's ownerPath reads <App> › <Page> like the client's. Without the option (prod builds) SSR output is unchanged. @solidjs/vite-plugin already passes the option for its dev and observe postures, so app server builds pick this up with no config change.

    Fixes ssrScope under 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/signals@2.0.0-rc.9

@solidjs/universal@2.0.0-rc.9

Patch Changes

  • 1643d2a: The universal renderer's spread() follows the @solidjs/web contract (spread() creates three reactive nodes per element #3388): ref folds 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 owned insert — that effect owns the child subtree — but a plain object whose children is 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. spread also 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 more mergeProps(() => …)), and several sources compile to the array instead of a mergeProps() call.
  • Updated dependencies [7623ce1]
  • Updated dependencies [af94f67]
  • Updated dependencies [5426ffb]
  • Updated dependencies [63560a1]
    • solid-js@2.0.0-rc.9

@solidjs/web@2.0.0-rc.9

Patch Changes

  • 17b0bda: Deprecate <Dynamic> in favor of dynamic()

    <Dynamic component={…}> is the same primitive as dynamic() with a worse shape: the tag travels in the props bag, so every instance merges component in at the call site, omits it back out inside, and builds a fresh dynamic() factory (with its memo) because a JSX wrapper has nowhere to hoist one. Polymorphic-component libraries end up omitting as, handing the tag to <Dynamic>, which merges it back in under component to omit it again. dynamic() has none of that for one extra line:

    // before
    <Dynamic component={multiline() ? RichTextEditor : "input"} value={value()} />;
    
    // after — hoist per component instance, or per module for a constant tag
    const Field = dynamic(() => (multiline() ? RichTextEditor : "input"));
    <Field value={value()} />;

    Dynamic and DynamicProps are 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 use dynamic(). The migration guide and control-flow RFC are updated accordingly.

  • 1af28a1: Call runHydrationEvents() after dynamic() 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() / Dynamic with a tag-name source honor an xmlns prop when creating the element (dynamic() / Dynamic with a string tag: no runHydrationEvents(), 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> with Link = 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=…/>. Like is, xmlns is 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. Without xmlns the namespace is still inferred from the tag name. Hydration is unaffected: it claims the parser-namespaced node.

    Types: xmlns is 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: ssrElement no longer emits style="" / 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 (ssrElement emits style="" / 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 — every Dynamic, 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 tryResolveString and resolveSSRNode. 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 a nested flag 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-Instance header (The server functions implementation adds a X-Server-Function-Instance header, 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 as context.instance (null for reads, as for no-JS callers). prepareRequest validation 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-Instance header 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 into context.instance for transformResult / 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.instance is gone from the hook contexts, and INSTANCE_HEADER is no longer exported from @solidjs/web/server-functions/{client,server}. A legacy header on an incoming request is ignored, as it already was. The prepareRequest validation (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-side observeServerFunctionCalls events keep their local instance id for pairing a request with its response.

  • 36db287: renderToString now disposes its reactive root synchronously before returning instead of via setTimeout, so a synchronous loop of renders no longer retains every graph until the next macrotask (renderToString disposes its root via setTimeout; 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 awaited renderToStream already uses — so httpStatus/httpHeader declarations still reach createSSRResponse. A render that throws leaves the head uncommitted and retracts its declarations as before.

  • 7623ce1: Server diagnostics on OBSERVE.diagnostics; OBSERVE.server owned by solid-js's server entry

    The 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.handling is fallback, client, or failed — the structured face of what renderToStream's onError receives, 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), and FRAME_MARKER_CORRUPTED from the frames client. Checks — dev-only guidance — convert every server console.warn to a code: SERVER_WRITE, REVEAL_IN_RENDER_TO_STRING, LAZY_ASSET_UNMAPPED, PRELOAD_DESCRIPTOR_INVALID, HEAD_TAG_INVALID, BEHAVIOR_CLAIM_DROPPED, and UNRECOGNIZED_INSERT_VALUE (now one code and a render kind on both platforms); ASYNC_OUTSIDE_LOADING_BOUNDARY on the server records with data.side: "server" before it throws. Server components are labelled for ownerPath (createComponent runs the body under a transparent <Name> owner in observe/dev — no hydration id consumed), so in <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; DiagnosticKind gains ssr, head, render.

    OBSERVE.server's objects (the invocation listener set, the trace-provider slot) are now created by solid-js's server entry, once per process under Symbol.for("solid-js/observe/server") on globalThis, rather than by @solidjs/web's module init: an observer's init() that imports only solid-js can subscribe and provide before 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 keeps server: {}; the client pays nothing.

  • af94f67: Server observe surface: OBSERVE.server and the invocation channel

    OBSERVE gains a server slot — an augmentable ServerObserve interface declared empty in @solidjs/signals (re-exported by solid-js), typed and emitted into by @solidjs/web's server runtime, so server-side observability consumers subscribe on the one OBSERVE object they already know from the client. The first channel is OBSERVE.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; wrapInvocation remains the single policy hook.

    @solidjs/web now publishes observe-tier server artifacts (dist/server.observe.js, server-functions/dist/server.observe.js, frames/dist/server.observe.js) under the observe export condition, alongside the existing dev/prod pairs. The surface and every emit site fold out of the prod artifacts.

  • 042b540: Trace context: getTraceContext(), W3C traceparent on the exchange, and the OBSERVE.server.trace provider slot

    The server runtime now reads the W3C Trace Context half of the HTTP exchange once per request — continuing an incoming traceparent (with tracestate/baggage beside it) or originating a trace when none came in — and exposes it through getTraceContext() 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, undefined outside both and on the client. Application code forwards a trace downstream with entries.traceparent. This is core HTTP behavior in every build tier.

    The trace is also handed down to the browser: entries are emitted as Server-Timing metrics (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), and commitResponseStub (which accepts the owning event in its options) — and as <meta name="…" content="…"> tags in the HTML shell head, delivered wherever the head content goes (</head> splice, onHead). A Server-Timing name the application already wrote is respected; Server-Timing now folds entry by entry when a stub and a response/responseInit both carry one. The browser is told only when something is recording the trace — the incoming traceparent was 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, entries merge 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): ref folds 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 owned insert — that effect owns the child subtree, and merging it would rebuild the children on every attribute change — but a plain object whose children is 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. spread also accepts an array of sources with an optional skip predicate — 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 server ssrElement array form.

  • 40977c9: ssrElement accepts an array of prop sources and an optional skip predicate: ssrElement(tag, [a, b, c], children, needsId, skip). The array form serializes straight from the sources with the exact output of ssrElement(tag, merge(a, b, c), ...) — later sources win per key, attributes land in merged order, only the winning source's getter is read (once), and skip(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]

    • solid-js@2.0.0-rc.9

test-integration@2.0.0-rc.9

Patch Changes

  • Updated dependencies [8d6de07]
  • Updated dependencies [17b0bda]
  • Updated dependencies [1af28a1]
  • Updated dependencies [c452850]
  • 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 [5b31076]
  • Updated dependencies [084e621]
  • Updated dependencies [76230f9]
  • Updated dependencies [632e45c]
  • Updated dependencies [ca05917]
  • 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 [5426ffb]
  • Updated dependencies [63560a1]
  • Updated dependencies [40977c9]
  • Updated dependencies [1643d2a]
    • @solidjs/babel-plugin@2.0.0-rc.9
    • @solidjs/web@2.0.0-rc.9
    • @solidjs/h@2.0.0-rc.9
    • @solidjs/signals@2.0.0-rc.9
    • solid-js@2.0.0-rc.9
    • @solidjs/universal@2.0.0-rc.9
    • @solidjs/html@2.0.0-rc.9

@github-actions
github-actions Bot force-pushed the changeset-release/next branch 30 times, most recently from 8d6ea98 to 235acfd Compare September 14, 2026 22:46
@github-actions
github-actions Bot force-pushed the changeset-release/next branch from 235acfd to 4ed68ac Compare September 14, 2026 23:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants