Patch Changes
-
617d195: Element claims fire after an element's initial attributes are applied, and the attributes whose writes re-claim are consumer-declared (#3923).
A claim consumer (a router's link-state layer) is told about each
a[href]/form[action]once per mount — with its dynamichref,target,rel, … already applied — instead of at creation before the binding effect's first run. Compiled output claims a static element at creation, an element with bindings at the tail of its binding effect's first run (under the owner captured at creation, since a held mount lands ownerless), andspreadclaims ana/formit applies after the first application (sodynamic("a")anchors are covered too). Writes before the mount claim never re-claim.Public API:
registerElementClaim(handler, options?)takes an options bag:{ attributes?: readonly string[] }names the attributes whose compiler-owned writes (setAttribute,setAttributeNS,setProperty, the spread's property path) re-claim an already claimed element — plain names;prop:hrefandxlink:hrefmap tohref. The write sites consult the union of every registered consumer's set. Default stays["href", "action"], so existing consumers keep today's behaviour. New exported typeElementClaimOptions.claimElement(node, owner?)(compiler-emitted primitive,@internal) takes the optional reactive owner the handlers run under.
Without a registered consumer every hook folds away: the three every-page size scenarios (
app: CSR,app: compiled CSR,app: compiled hydrating) are byte-identical. -
56c309c: Direct server-function calls no longer report thrown
ResponseorResponseEnvelopevalues asserver-function/thrownfailures, matching HTTP dispatch. Callers still receive the response or the envelope's value. A response that reaches a boundary or the render root is still reported by the renderer. -
a497d4b: Remove
state,noscroll,replace, andpreloadfrom core anchor JSX types. They are not standard<a>attributes; a routing integration declares them by augmentingAnchorHTMLAttributes.linkstays — it is the shared explicit-links opt-in, and the SSR claim bag already copies that name. Media and webviewpreloadattributes are unchanged. Types only. -
4afc662: Move
dynamicComponentintosolid-js.@solidjs/webkeepsdynamicand re-exportsdynamicComponent. -
a14c18b: A 304 from a server function no longer stamps a body-format header onto the cache update, so a browser replaying a cached GET still decodes the stored value.
-
372005f: The client runtime restores the native submission value for checkbox and radio inputs by removing the
valueattribute whenvalueordefaultValueis nullish. Checked inputs then submit"on". Explicit empty values are preserved, and text inputs and textareas still clear to"". -
469733e: Fix aborting a document that renders a live server component crashing Node: the document's
sc:livechannel now marks itself closed when the abort cancels it, so the render's own end (and any late hole op) no longer closes or writes the cancelled stream, which escaped as an unhandledERR_INVALID_STATErejection. -
43570f8: Reapply a select's bound value when its options arrive after the value was written.
-
1365d31: Render input
defaultValue/defaultCheckedand optiondefaultSelectedsupplied through SSR spreads asvalue,checkedandselected. Within prop sources, a default takes precedence over the bound value in the initial HTML. Named attributes compiled after the spread retain precedence. -
06c7717: A server component that throws during its synchronous render is no longer cacheable.
respond(View, { headers })puts those headers on the frame response, and the response is a 200 whose body carries the error, so an author'scache-control: public, max-agestored the failure. When the render fails before the head is built, the frame response sendsCache-Control: no-storeand drops the CDN-targeted fields that would override it at a CDN (CDN-Cache-Control,<CDN>-CDN-Cache-Control,Surrogate-Control). The status stays 200 and the body still carries the error record. A render that succeeds keeps the author's headers, and so does a returned{ error }value: the author chose that value and the headers that travel with it. A failure that lands after a suspended shell has already left can no longer change the head.@solidjs/web/frames/serverno longer exportsrenderServerComponent,renderToFrameStream,serverComponentResponseorcreateFrameSink. They were internal building blocks: a server component reaches the client by being returned from a server function (directly or wrapped inrespond()), and the transform hooks (frameTransformResult,frameTransformFlightResult,frameTransformDirectResult) render and frame it. The entry is experimental, and the hooks and document-shell exports are unchanged. -
abe12fc: Frames client: the adopt-time slot walk reads the server's tier announcement for the scan, not only the load — a page whose
sc:tiersrecord names other tiers and notbindis never scanned for binding-slot markers (_s:*), and the bind tier is never loaded for it; an un-announced page keeps detection.collectSlotsis one TreeWalker pass over comments (and elements only when markers may exist), testing a comment's data by prefix before any regex. Same slots found, in the same order; no public API change. Measured on the HN story page (652 occurrences, 17k comments):collectSlots14.2 → 5.4 ms. -
f64fcfe: Fix an unbounded request loop when a server component's re-asked flight fails the same way as the error it replaces. The server writes an escaped render failure as its message string, so two flights that fail alike carry equal payloads; a mount's content node told "already surfaced" from "the next flight's error" by the payload and re-asked on every flight instead of surfacing the error. It now keys a surfaced error by the response version that carried it:
reset(), and a fresh mount over an errored address (e.g. after a hover preload that failed), make one request and the error reaches the nearest<Errored>again. -
e5a4c8e: fix(web):
hydrate()started mid-parse waits for the end of the shell (#3971)An
asyncclient entry can run while the browser is still parsing the server shell.hydrate()then gathered only the_hknodes parsed so far, created fresh client nodes for the rest, and left the server copies parsed afterwards in the document, dead (no listeners, no bindings).A render that emits
<HydrationScript />now ends its shell with a small marker script, after its records, and its bootstrap declares the ledger the marker writes to. Whenhydrate()starts before that marker has run and the parser is still working, it parks until the marker runs (orDOMContentLoaded, if none comes), then hydrates against the complete shell. Streamed fragments arrive after the shell and are claimed as before, so streaming still hydrates before the stream ends. No API change. A host-placed bootstrap (generateHydrationScript(): hand-built documents, islands hosts such as Astro that await each island's children themselves) declares no ledger and never parks; renders without<HydrationScript />andnoScriptsrenders are byte-identical. -
6232d18: A server component's markup hole that contains an element with a dynamic attribute is no longer sent twice. The first render writes a
data-lhaaddress on such an element and the sweep's re-evaluation does not, so the unchanged hole compared as changed and its markup shipped again as aholerecord on the document's live channel. The baseline now ignores those addresses, as it already ignored thelhcomment markers. The markup that ships is unchanged. -
3173a14: A server component factory mounted more than once keeps an independent hydrated client slot at each site. The first site still uses the function id; each later site gets its own frame scope so hydration keys, slot records, and adoption do not collide.
-
5d75ed8: A nested server region keeps its own stream version, so a same-argument refetch no longer makes the next argument's region look stale.
-
86ac6da: Frames (document adoption): gather an adopted boundary's hydration keys once per adoption, not once per occurrence. Each adopted occurrence's claim window used to select
[_hk^="<prefix>"]over the whole hydration root — 37 ms on a 652-occurrence comment thread, more than the rest of its hydration. The boundary now indexes the_hknodes under its element once (bucketed by occurrence prefix) and serves every window — the occurrence's and a streamed<Loading>'s resume inside it — from that index; a fragment revealed into the element later extends the index for the revealed parent, so late (post-done) claims still find their nodes. The page-level gather and the streamed boundary's resume path are unchanged. -
c2d4571: A synchronous
prepareRequesthook no longer defers the server-function request by a microtask, so reads adopted during hydration stay inside the hydration trace instead of refetching from the browser (#3959). An async hook is still awaited. -
49bed5c:
respond()is typed as its value and its envelope is a realResponse; direct calls unwrap it.- Direct calls unwrap envelopes. A server function called in-process during SSR that returned
respond(value)handed its caller theResponseEnvelopeitself, while an HTTP caller decodedvalue— and a server component wrapped for its headers (respond(View, { headers })) reached the render as an object, so the frames policy never branded it and the hydration serializer failed on the envelope. The direct leg now resolves with the value (a thrown envelope rejects with it) beforetransformDirectResultruns. Of the metadata,Set-Cookieis appended to the render's response head; other headers describe the function's own address and the status is the page's, so neither is applied to the document. - The envelope is a
Response.ResponseEnvelopenow extendsResponse, carrying the given response's body, status and headers, so a consumer with no Solid knowledge (a fetch-style API dispatch) answers with it as-is instead of JSON-encoding the wrapper.new ResponseEnvelope(response, value)keeps its signature;responseis the envelope itself, orundefinedwhen constructed without one. Solid's own consumers still recognize it withisResponseEnvelope(). - The helpers are typed by what they mean to the caller.
respond<T>()returnsT, andredirect()/reload()are<T = never>(…): T— control flow, not values, so they vanish from the returning function's inferred type, while an annotated return or aResponse-typed request handler suppliesT(a literalneverwould also mark code after a bare call unreachable). A bare"use server"function's own signature is therefore its callers' type, with no wrapper narrowing (NarrowResponse) needed. At runtime the helpers are unchanged.
- Direct calls unwrap envelopes. A server function called in-process during SSR that returned
-
43570f8: Keep every bound value of a multi-select selected when its options arrive later.
-
b749474: Pin
serovalandseroval-pluginsto exactly1.6.7: 1.6.8 changed the stream representation in a patch release and grew bundles. Stream detection now requires a real stream — the JSON decoder (server functions, frames) treats a value as a stream only when seroval'sisStreamholds and it has a callable.on, so decoded plain data that happens to carry a__SEROVAL_STREAM__key stays data. The frames container-trace check, the client container-trace materializer and the render stream's channel guard also recognize seroval's untagged stream class, and the frames border walk tests for an async iterator first. -
234cfc9: The server-component half of hydration installs from
@solidjs/web/frames'installServerComponents(), not fromhydrate():sharedConfig.holdBoundary(the client hold on adopted markup counted as a pending boundary),sharedConfig.hydrateWindow(the claim window an adopted occurrence re-enters hydration through), fragment ownership by rendering (the_$HY.faterm of the reveal policy) and the ledger's published answer_$HY.fron the solid side; the declared claim roots (sharedConfig.claimRoots) and the frame-region exclusion of the root sweep on the DOM runtime's side. A hydrating page without server components carries none of it — −686 B minified / ≈ −205…−243 B brotli on the hydrating apps (this returns the bytes the frames A0 correctness pass added to every hydrating page); a server-component page pays the two installers' glue (≈ +220 B minified).Public surface (
@internal):enableServerComponentHydration()onsolid-js(runtime) andsolid-js/internal(typed), a no-op on the server entry;installServerComponentHydration()on@solidjs/web, which installs both halves — the frames client calls it where it installs its reveal hook. -
be845cb: A
GET(orHEAD) server-function response whoseCache-Controllets a shared cache store it (publicors-maxage, unlessno-storeor an unqualifiedprivateis also set), or whoseCDN-Cache-Controla CDN may store from, no longer carries the request's trace inServer-Timing(traceparentand any provider entries), so one request's trace is not replayed to every viewer from the cache (#3985).POSTresponses and documents are unchanged. -
a26ee62:
HydrationScript,generateHydrationScriptandcreateSSRResponsenow take the sameCSPNonceasrenderToString/renderToStream(a string or a{ script, style }pair) and use its script half, soevent.noncecan be passed to all of them as is.scriptNonceandstyleNonce, which only existed to do that projection, are now internal: they are no longer documented, and stay exported only for@solidjs/vite-plugin's generated handler until it stops importing them. -
22f942d: SSR: a router can mark links in server HTML (#3878). Element claims never fire during SSR, so plain anchors arrived without
aria-current="page"/data-active— late on hydrated pages, never on pages that do not hydrate. Both compilers now give every candidate<a href>in SSR output one hole after its attributes,ssrLinkClaim(attrs), where a render's link handler writes the anchor's link state and""otherwise. A static anchor's hole is one eager call over a hoisted attributes object (no allocation per render); a dynamic anchor's joins the element's attribute group and reuses the raw value itshrefhole evaluated. Anchors the compiler can rule out — a static non-emptytarget, adownload, arelnamingexternal, an author-writtenaria-current, an empty href or a non-HTTP scheme — get no hole at all. The hole creates no owner and takes no hydration id. A spread anchor's link attributes are collected byssrElementfrom its winning sources (an anchor's trailing link attributes stay a source instead of baked tail markup).New server API (
@experimental):setLinkClaim(handler)sets the handler of the render the caller belongs to — found through the owner chain likegetHydrationWriter(), so concurrent renders never share one — and returnsfalseoutside a render;undefinedclears it. The handler,(attrs) => string, receives the anchor's link-relevant attributes (href,target,rel,download,link,aria-current, raw values as written) and returns its attribute markup. An author-writtenaria-currentalways wins. Server components inline in the document render under the page's handler; a server component's own frame stream has none unless its render sets one.Trust marker: the first
setLinkClaimof a render writes thelinkshydration record once (_$HY.r.links = 1, with the shell's hydration script on the document face, a data record on a frame stream).hasServerLinkState()(client;falseon the server) reads it non-destructively, so a link consumer can tell "this page's anchors carry the server's link state" from "no handler ran" and trust the HTML at hydration instead of resolving every anchor's URL at claim. Without a handler the HTML and the hydration ids are byte-for-byte what they were. -
39411bd: Escape every
<inuseHeadJSON script bodies as\u003C. JSON here meansimportmap,speculationrulesand the JSON MIME types (application/json,text/json, any+jsonsubtype such asapplication/ld+json). Only</scriptwas rewritten before, so a JSON-LD value like<!--<script>put the HTML tokenizer in the double-escaped state, and the markup after the script became script text up to a later</script>or the end of the document. The escaped form parses back to the same JSON. The client writes and compares the same text, so hydration adopts the server's element instead of replacing it; a</script>inside JSON no longer causes that replacement either. Other scripts and data blocks keep their current body. -
234cfc9:
readShallow(the compiler's one-layer tracked read of an object-valuedclass/style) reads a proxy's keys withReflect.ownKeysdirectly — whatsourceKeys(value, SOURCE_PROXY)resolves to by definition — instead of throughsourceKeys, whose runtime kind argument kept the merge/omit view walkers of@solidjs/signalsin every compiled page with a dynamicclassorstyle, reached by nothing. Identical behaviour; −1,165 B minified / ≈ −350 B brotli on the compiled server-component page (a page with an element spread keeps the walkers throughspread()). -
Updated dependencies [4afc662]
-
Updated dependencies [b460640]
-
Updated dependencies [39450e5]
-
Updated dependencies [cbd66e5]
-
Updated dependencies [b083833]
-
Updated dependencies [5413c06]
-
Updated dependencies [0c4bff7]
-
Updated dependencies [25935fe]
-
Updated dependencies [b749474]
-
Updated dependencies [234cfc9]
-
Updated dependencies [d966729]
-
Updated dependencies [a405039]
-
Updated dependencies [234cfc9]
- solid-js@2.0.0-rc.15