Patch Changes
-
3d8a8a9: Point JSDoc
@descriptionlinks at the 2.0 reference docs (v2.solidjs.com), documentisPending, point the console footer, diagnostics guide and cheatsheet links at thenextbranch, and replace the staleonMountadvice in the server-side client-only API error. -
3cffd86: Async-mode renders (
await renderToStream(...)) now produce the same bytes for the same data, whatever order the async sources settle in (#4028). Once the thenable is taken, hydration writes and the pending-promise stub batch are held until the serializer flush and handed to seroval sorted by hydration key, so reference ids and resolution chunks no longer follow settle order. A body-digest ETag now matches across renders whose only difference was API latency. Streaming consumers (pipe/pipeTo/readable) and custom serializers/sinks are unchanged. -
86cf504:
data-*attributes type-check in props objects (ComponentProps<"div">,JSX.HTMLAttributes<T>), not only in JSX: a template-literal index signature onElementAttributes(#4082). -
4a99c84:
effectandinserttakelocbesidenamein their options, andspreada trailinglocafter its tag — the source site the compiler emits undersourceNameslocations— so a diagnostic about a compiled binding reportsat src/App.tsx:14:7and carriesevent.loc. Dev and observe builds keep it on the node; production ignores it. -
96b615d: Remove the internal
scriptNonce/styleNonceexports from the server build. They stayed exported after #4016 only because@solidjs/vite-plugin≤ 3.0.0-next.49 imported them. 3.0.0-next.50 projects the nonce itself (solidjs/solid-vite-plugin#409). Apps must use@solidjs/vite-plugin3.0.0-next.50 or later with this release. -
8092282: fix(frames): a link inside nested slot regions is claimed once per adoption instead of once per enclosing region, and a streaming document whose server components mint no tier writes an empty
sc:tiersrecord so the client skips its per-element slot-marker scan -
8163bc3: Send data server functions called during hydration on a page with server components (#4033). The frames client holds a server-function call while the document may still deliver a boundary for it, and a data function never has one. A wait created under a hydration trace cached the trace's never-settling
Promisestub, so every later call for that function hung; and a call made after the page's last reveal (a hydration-end recompute) was released only by a reveal that never came. Waits made under a trace are no longer kept, and outstanding waits are released when hydration ends as well as on reveals. -
1bcee91: Server components hydrate their client slot occurrences on demand. The adoption pass mounts none of them; an occurrence mounts when an event lands in it (outermost first, before the event reaches a handler), before any signal or store write applies (every remaining occurrence, against the server's state), or in idle slices after the load event. Hydration stays in progress until the last one has mounted. The write side is an internal pre-write hook in
@solidjs/signals, tree-shaken from apps without server components. -
47a217f: Frames: a signal or store write during deferred hydration mounts only the slot occurrences the server recorded as its readers, then applies, instead of mounting every pending occurrence first. The server names plain signals (
<owner id>~<n>) and plain stores (by their root) in server-component renders and emitssc:depsrecords per frame; writes it cannot vouch for (unnamed, derived, optimistic, projection or wide sources) still mount everything first. Apps without server components keep their sizes and code paths. -
7ad2342: A server component consumer that mounts while its frame response is already streaming no longer reports a false
ASYNC_WATERFALL(#4140). The landing promise the mount awaits now carries the request's start (OBSERVE.attribution.markFlight): the server-function client hands its response handlerctx.startedAt, the frame host stamps the landing it mints for an open flight, and the mount's wrapper inherits that stamp. A re-ask after an error is still new work. Observe and dev builds only; production output is unchanged. -
5bc0e5e: Frames: the live-hole and attribute-hole appliers move out of the eager frames client into the lazy
wiretier (about 280 B brotli off every server-components page). The server announceswirewhenever a response or document carries a live hole, so the tier loads alongside the first hole update; a hole record that arrives before the tier is resident waits and is applied when it installs. Row ops are still sent only on live responses. -
2aa9e86: Frames: the range morph (the reconcile that lands a refetched or navigated root over shown content) moves out of the eager frames client into the lazy
wiretier, about 790 B brotli off every server-components page. A frame call on a page that shows frames starts the tier's load with its request (so a router's hover preload warms it), and a root landing over shown content waits for the tier; adoption and first materialize never morph and never load it. -
9c8930c: Frames: a write that carries only slot records mounts or updates the occurrences it names from the frame's last slot walk instead of walking the whole frame again. A document's records arrive one write per occurrence, so eager hydration walked a page of N occurrences N times (401 walks of a 7,948-element frame on a 400-card page, about 1 s at 4x CPU); it now walks once. Any content change drops the cached walk, and an occurrence the walk did not see falls back to the full sync. A flush's content passes (assets, segments, live holes) now walk only the store's non-slot keys instead of enumerating every record on every write, and deferred hydration keeps its walk across record writes, touching only the occurrences a write names.
-
e4edd34: Frames: the frame stream now carries versioned batches of keyed records (
{ id, version, r }), the same writes the client's resident store applies, instead of eleven typed chunks the client translated into records. The server writes records natively:renderServerComponent,renderToFrameStreamand the response helpers pipe these wire messages, so there is one format from the producer to the store. Records written in the same synchronous flush leave as one message per frame (a segment's assets, content and reveal travel together; a sweep stays one batch), with nothing held across a flush. Onlystart,dataand the single-flightoutcomekeep atype, and the document face's live channel carries the same batches. The eager frames client loses its translation step (about 0.2 KB brotli off every server-components page). Experimental wire format: a client and server must come from the same release. -
10b3f55: Frames client: a live-hole apply no longer searches the frame for its marker on every write. Each hole's target is cached between applies and reused while it is still the frame's and still matches; otherwise the search runs as before.
-
1f3b5b4: fix(frames): a frame rendered off the live address marks only the holes that can change within its response (the document face's gate), so synchronous holes ship without
lhmarkers,data-lhaaddresses or hole digests; the live address keeps every marker for its reconnect have-list -
ccd9948: Smaller floor bundles: the optional engines (lanes, verdicts, boundaries, stores, affects, external sources) install their scheduler hooks as module variables, so an app that never loads them no longer retains their paths in
recompute,readand the settle step; client-onlyrenderno longer retains hydration text adoption, and the delegated dispatcher drops dead guards. The JSX hello world goes from 10.35 KB to 9.75 KB brotli. -
fda29a4: An insert decides whether its inner effect's first run is inline (the first render) or scheduled into the effect phase (any later render) from a flag, not from the previous computed value. A hole that was an element's only child normalizes to
undefinedwhile it renders nothing, which read as "never ran": content arriving there later — a<Show>flipping to a branch whose reads are held by an action — ran its inner effect inline, mid-compute, and mounted ahead of the hold (an enabled, empty input before the commit, against D-23). Multi-slot holes, whose normalized value is always an array, already scheduled correctly; single-child holes now match. -
0e0c043: Write one
data-lhaaddress on an element whose attribute was still pending inside a<Loading>in a live server component (#4032). When the in-tag hole escalated, the attribute capture was dropped but the address it had spliced into the tag stayed, and the boundary's resume minted a second one in front of it. The stale address rode every later attr op for the element, so the first op moved the element off its bound address and every op after it was dropped. The address now goes with the dropped capture. -
0a49dd8: Live holes: on a live response, a sweep that changed some rows of a
<For>sends just those rows (replace, append, truncate by index), and the frames client morphs only their ranges. A client whose range doesn't show what the rows were taken against resumes its live connection, which sends the hole whole. -
347f242: New dev check
SSR_LIVE_HOLE_LATCHED(#4023): a live markup hole that shipped its markers and then stopped updating, because re-running it would create reactive scopes (most often a compiler condition memo for{m.usage && …}), is now named on the console, located by its component, instead of going quiet. Behavior is unchanged; hoisting the property into a local keeps the hole live. -
c5da908: A live hole whose content calls a slot keeps updating. Before, the first re-run that called a slot latched the hole (
SSR_LIVE_HOLE_LATCHED, cause "records"). Now each occurrence's identity belongs to its hole: it is its position there, or its$key. Calls outside any hole keep the ids they had. A re-run sends a record only for a new occurrence or for args that changed. On the client, the hole's morph resolves slot ranges against the frame's displaced-range index, the same way the root morph does. An occurrence that survives keeps its range, its interior and its mounted component, even when it moves into another hole. New occurrences mount and the ones that are gone dispose. Under deferred hydration, an occurrence that is not mounted yet claims its markup with the record that markup was rendered from, then takes the newer one. A live attribute that calls a slot still latches, and so does a re-run that creates owners. -
b972757: Live holes keep updating when their markup creates reactive scopes (a nested
<For>, a compiler condition memo such as{m.usage && …}, a component). Each re-render runs in a throwaway scope disposed once its markup is out; only a scope still waiting on async work closes the hole (#4023). -
8e2590e: Hydrating a
<Loading>whoselazy()module lands after hydration starts no longer moves its server-rendered nodes when the boundary has a sibling in the same fragment (#4049). A sibling before the boundary used to swap the boundary's static and lazy children (A/C/B instead of A/B/C), and a sibling after it detached and re-attached the boundary's content, restarting CSS animations and blurring a focused input. The late claim now leaves server nodes in place when they already sit in the region, in order, around the nodes the insert was tracking. -
8fbadfd: Frames morph: when a text hole empties, the reconcile drops the stale text node instead of relocating the next element in front of it, so the elements after an emptied text hole stay put instead of being re-inserted.
-
e1bf2af: Frames morph skips an identical subtree with one native
isEqualNodecompare instead of walking it. -
e43e22f: Server components: a client fill nested in server content that another fill places (a region) now sees the context that enclosing fill provides, on the server render, at hydration (eager or deferred, including an inner occurrence mounting first) and for occurrences a stream or live hole adds later. On the server, a fill called while an enclosing occurrence's args evaluate renders when the region is placed, under the placing owner; the region's usage flip runs after the fill's output has resolved, so content placed inside a provider (whose children resolve in the walk) ships as markup instead of an occluded record. On the client, a nested fill mounts under the owner that read the region, waits while the region is unplaced, and mounts afresh at the next placement after one is disposed. A fill's two claim windows now share one claim owner, so a fill rooted at a context provider claims its server markup instead of rendering a detached copy.
-
8e2446f: The keyed reconciler is back on the slot-ownership design from May: the common-prefix and common-suffix walks match by identity, with no per-node liveness check. #574 — a node dropped after a replace plus an end-swap — is fixed at its cause instead: the suffix walk is bounded to the open range, so it can no longer reach back into entries the pass already consumed. The append branch anchors on a node the slot still owns, so a node user code moved away cannot make an insert throw. In the map branch, an old node that is no longer ours is consumed rather than "replaced" by an insert at the region's end, so the changed window lands in list order around a moved-away node. A node moved away that sits in the unchanged prefix or suffix stays where it was moved, as it did before the #574 patch. A 1000-row swap drops from about 30 to 25 µs; long reorders and memo-wall's one-change update 10–50%.
-
7f8ab2d: A server function reference can declare its own client send with
withMeta(fn, { fetch: (address, init, next) => ... }). The transport uses it for that function's requests, with the configured send (the app'sfetch, or the global) asnext, so an integration that answers some calls itself (a prerendered build's static artifacts) no longer competes with the app'sconfigureServerFunctionsClient({ fetch }). Exported typeServerFunctionsSend. -
3f36f77: Server components: binding slots are removed. Client components in slots replace them: place a client component through a
Slot(<props.x />) where the server's markup needs client behavior, or a formactionfor a mutation. Removed with them: theBindingSlot,SlotOutputandSlotErrortypes, the lazy@solidjs/web/frames/bindentry and its"bind"tier, the_s:*position markers and the compiledssrClaimhole, the effect of theserverComponentscompiler option (@solidjs/compilerand@solidjs/babel-pluginstill accept it, so existing build configs keep working, and ignore it), and theBINDING_SLOT_POSITIONdiagnostic code (@solidjs/signals'DiagnosticCode,solid-js'sdiagnostics.json). Ordinary slots, theirslot:<id>ranges and records, and$keyare unchanged. -
8e2590e:
respond(value, init)accepts every value a plain return does (#4060). Its passthrough JSON body — read only by consumers without the client runtime — is now encoded when read instead of when the envelope is built, so abigintno longer throws insiderespond()and fails both the direct call and the HTTP call. Unscripted HTTP callers get the carried JSON body only when JSON carries the value faithfully; otherwise the value is encoded the same way as a plain return. -
8bd7a5b: Bind each server-component site to its own frame when the server minted the sites in a different order than the client mounts them (#4042). Document adoption took sites of one server function by ordinal (
id,id~2, …), so a site behind async setup, rendered after its neighbours on the server but mounted first on the client, adopted a neighbour's element, and its first update landed in the wrong frame. A document frame for a call with arguments now carries the call's argument hash (data-fa), and a mount takes the unclaimed site showing its own arguments, falling back to the first unclaimed site when none does. Argless calls render byte-identical markup. -
9ac1481: The server error hook (
configureServerErrors, a render'sonError) now hears each error object once per request — once per render when no request is in scope — instead of once per process (#4125). Reusing oneErrorobject across requests (a module-level constant, a cached rejection) used to notify only the first request's hook and hand later requests the first request's mapped wire value; each request now gets its own report and its own answer. Within a request the hook hears an error once perkind: a direct server-function call that throws during SSR is heard asserver-function, and the<Errored>or<Loading>that contains it is heard asrender, so a render'sonErroralone tells you the render failed. Every road still carries the first mapped value. -
3e94215: Server functions: arguments never decode
ResponseorRequestvalues. The handler decodes client → server argument payloads with the configured plugin list minus theResponse/Requestplugins, so a call carrying one — top level, nested, or inside a promise or stream — is refused with400before the function runs.enableRichArguments()rejects such a call before sending. Results and hydration keep the full plugin set.Server-function responses now default to
X-Content-Type-Options: nosniff; a value the function sets itself is kept. -
a61c929: A server component in a document marks its live holes only once the render has served something a later commit can replace: an async iterable it pumps, or a loading value or seed shown ahead of its landing. Until then a hole that resolves synchronously ships without
lhmarkers,data-lhaaddresses or a ledger binding, because no channel op can ever address it. On a request/response page that is every hole: the 1,406-comment hackernews thread drops from 948 KB to 787 KB and renders in about half the server time. -
36039e2: The shell-end marker is spliced in before
</body>instead of being appended after</html>when the shell is a whole document (#4030). Async render mode and a stream with nothing pending now end with</html>again; a shell without</body>(an embedded render) still gets the marker appended. -
46db1cf:
renderToStreamwaits one task before flushing the shell while fragments are still pending, so a read whose retry chain outruns the microtask drain (a server function over in-memory data, a cache answering onsetImmediate, I/O that already completed) renders inline instead of shipping itsLoadingfallback, a<template>and a reveal that shifts the page. Renders with nothing pending flush exactly as before; genuinely pending I/O pays one task of first-byte latency. -
3d8a8a9: Ship the Solid 2.0 primer for coding agents as the
solid-2.0skill, emitCHEATSHEET.mdas thesolid-2.0-cheatsheetskill on build, give every shipped skill thename/descriptionfrontmatter the Agent Skills format requires, and add thetanstack-intentkeyword so skill registries discover them innode_modules. -
8d4a285: A cleanup that throws while a stream is torn down no longer crashes the server (#4029). Aborting
renderToStream'ssignaldisposes the render inside theabortlistener, where a throw became an uncaught exception; the teardown now reports it throughonErroras a failed render (handling: "failed") and finishes. -
8268cfd: Server trace and span ids (the W3C
traceparenta render mints when the request carries none) are drawn fromMath.randominstead ofcrypto.getRandomValues. The ids need to be unique and well spread, not unguessable, which is also OpenTelemetry's default; the twogetRandomValuescalls cost about 10 µs per streamed render on Node, around 14% of the CPU between a request and its first chunk. -
Updated dependencies [3d8a8a9]
-
Updated dependencies [1b81476]
-
Updated dependencies [b8617ec]
-
Updated dependencies [4a99c84]
-
Updated dependencies [0f64d82]
-
Updated dependencies [467ac97]
-
Updated dependencies [1bcee91]
-
Updated dependencies [47a217f]
-
Updated dependencies [dbc5d00]
-
Updated dependencies [7c36f2b]
-
Updated dependencies [0a49dd8]
-
Updated dependencies [51d005a]
-
Updated dependencies [c5da908]
-
Updated dependencies [3f36f77]
-
Updated dependencies [eb4e019]
-
Updated dependencies [dfb31ed]
-
Updated dependencies [9ac1481]
-
Updated dependencies [a61c929]
-
Updated dependencies [3d8a8a9]
-
Updated dependencies [3d8a8a9]
-
Updated dependencies [4ff6580]
- solid-js@2.0.0-rc.16