github solidjs/solid @solidjs/web@2.0.0-rc.22

pre-release36 minutes ago

Patch Changes

  • 366c0eb: renderToStream().pipe() no longer throws buffer.flush is not a function when the render is abandoned from inside its first write (#4745). With a shell of 16384 characters or more, a pre-flush redirect in createSSRResponse, a sink whose write throws or a signal aborted from inside that write left pipe flushing a buffer that had no flush, and the unhandled rejection ended the process under Node's default handling.

  • 90d1a51: A viewer that joins a running broadcast is sent each live range once, as it shows now. Its snapshot used to carry the frame as it first rendered and then every changed hole whole again, so a late joiner downloaded a big live table twice (about 600 ms to join a 2,000-row table, now about 120 ms).

  • 3ce08e9: A string class value no longer stores a property on the element when it is rendered on the client. className() cleared its applied-classes snapshot with node._$classes = undefined before every string write, which added an own property to each element the first time its class was set, even though only an object class value ever fills that snapshot. It now clears the snapshot only when one exists. Mounting 1,000 table rows with six string class bindings each allocates about 118 KB less (1,793 KB to 1,675 KB), and a uibench tree render is 5 to 10% faster. Behavior is unchanged. The hydrating path still seeds the snapshot as before.

  • 1212095: Serializing a value that holds a cycle no longer copies the parts of it that have nothing to replace. An object an async memo answered with and a store also held stayed one object in the hydration data only when the store had no cycle; with one, the client got two copies (#4846).

  • 8506dbf: fix(web): a deferStream source that rejects on the server hydrates as a rejection, so reset() refetches it on the client (#4747)

  • bd38a8f: Delegated events no longer keep an unmounted component alive through currentTarget. Event delegation simulated currentTarget with a getter created for each event, and V8 can keep the first such getter in the shape every event of that type shares, along with the event, its target and the component around it. currentTarget is now set as a plain value when a delegated handler runs.

  • c927463: fix(web): useHead tags of content behind a boundary's fallback are withdrawn while the fallback shows (an <Errored> keeps its content alive for reset(), so the error page kept the failed page's title) and come back when the content reveals (#4873)

  • 4ba1731: A controlled input no longer loses typed text when its signal write is held (an async derivation of it is pending) and another binding in the same template re-runs, such as a sibling isPending class flipping: a stateful DOM prop (value, checked, …) in a grouped effect or a spread skips an unchanged write while the effect is a stale reader of an open hold, and the hold's landing writes the bound value (#4872). Outside a hold every run still writes (#2016, #4186).

  • 803d269: feat(web): dev warns INVALID_HTML_NESTING when hydration claims an element the browser's HTML parser moved out of its parent (invalid nesting across a component boundary: <div> in <p>, <a> in <a>, <tr> straight in <table>); the key-miss warning for a <form> names a form nested in a form (#4875)

  • 54456c9: A live server component's attribute whose value is still pending at the top of the render (no <Loading> around it) no longer writes the live-hole comment markers inside the tag or the attribute value (<div<!--lh:1--> style="…"<!--lh:/1-->>), which broke the element. The element keeps its attribute address, and the binding registers once the tag's pending values resolve, so later values update the attribute as an attr op.

  • c50abb6: A dynamic mount of a live server component that switches arguments and comes back to its first call (a windowed view scrolling back to the slice the page was served with) shows that call again. The equals gate compared the incoming binding with the one the memo holds, which is the first resolution's, so the way back read as "nothing changed" and the mount stayed on the other address.

    A live connection cancelled before its body's start arrived now reads as a death, and the loop reconnects. A remount replaying a warm store gives up on a row-op record by cancelling the new connection so the resume re-sends the hole whole; with nothing open yet, the end read as a completion and the mount was left with no live connection.

  • f675c66: fix(web): a server function mutation with nothing folded and no metadata still reaches every flight consumer, so a router revalidates after a direct call or a core action (#4883)

  • 5a05b21: A disposed Portal no longer stays reachable through a live node of the same shape: every portal node shares one _$host getter that reads the slot's accessor from the node (#4852).

  • 246a491: insert no longer carries the portal host tagging in a bundle that renders no <Portal>: Portal installs it on first use. A hand-written insert(..., { host }) outside Portal now needs installHostTagging() first.

  • 32b4294: Fail renderToStream when the default serializer reports a synchronous error before the first flush, matching asynchronous failures. No HTML is sent, onCompleteAll is skipped, and createSSRResponse returns 500 when consuming the stream. An error an <Errored> contained does not fail the render when it cannot be serialized: its record and the rejection of its source, deferStream or streamed, are sent as a plain Error with its message, the fallback stays, and the error hook hears the failure once. A rejection nothing contained still fails the render.

  • 218fe4a: The DOM list reconciler no longer throws NotFoundError when user code moved a list node past the end of its list and the same update removes or replaces that node. The append step used the node after the list as its anchor even after the pass had taken it out of the parent; it now falls back to the marker, or the end of the parent.

  • dfb1812: The DOM list reconciler does less work for common small edits. A node moved from one end of a list to the other takes one DOM move, a list whose nodes did not change returns before reading the parent, and the window map is an index stamp on the new nodes instead of a Map. On the Octane board js-framework's reorder operations take about 20% less time and a text hole beside sibling nodes updates about 13% faster. Bundles with the web runtime are 33 bytes smaller minified.

  • b38d592: List reconciliation moves every connected node with Element.moveBefore() where the browser has it, not only the node holding focus, and no longer uses replaceChild() there. Moved rows stay connected, so focus and other node state survive any reorder, and a reorder places nodes instead of detaching and reattaching them: dbmon's 1,000-row sort drops from ~2.0 ms to ~0.75 ms, and shuffles and reverses get 3–7% faster. The per-pass document.activeElement read is gone. New nodes and browsers without moveBefore() keep insertBefore() as before.

  • 9c917ea: A ref callback that reads a sync memo on a row mounted while an async memo is in flight no longer leaves that update held forever (#4767). Refs run in the attribute effect's commit half with no owner; a memo born into the hold has only a staged value, and an ownerless read of it threw NotReadyError (A19 exception 1), which errored the effect mid-hold so the transaction never landed. runRefCallback now marks the read, and serve returns the staged value only when the ref's creating pass was born into that memo's hold (F-7); production ref() always goes through runRefCallback so the mark is present outside dev too.

  • 7230c08: A ref callback (ref={el => …}) now runs the way a render effect's callback runs at creation (#4761). It is untracked and runs in the context of the pass that creates the element, so its reads see what that pass is building. Before, it ran with no owner, so its reads were outside reads that saw the screen before the pass committed. A non-keyed <Show>/<Match> branch that an optimistic write had hidden could not come back: its ref read the narrowed accessor, got the old falsy condition, and threw a stale read that halted the app.

    A ref still owns nothing. In dev, creating a memo, effect or root inside it throws PRIMITIVE_IN_EFFECT_CALLBACK, and onCleanup inside it throws CLEANUP_IN_FORBIDDEN_SCOPE. Set those up in the factory that returns the ref, which runs in the component body. Writes, refresh, action calls and onFlush stay allowed in a ref, as in an effect callback.

  • c8c5eb1: Server rendering only treats nodes the runtime made as finished markup. ssr(), ssrElement() and the frames sink now stamp their nodes with the Symbol.for("solid.ssrNode") symbol (as $, a value no data can hold), and every insert position checks it instead of the object's shape. A plain object with a t key (or t, h and p), such as one parsed from JSON, is data: it renders nothing and gets the dev warning UNRECOGNIZED_INSERT_VALUE, like any other plain object. The async-memo landing check (ASYNC_MEMO_ELEMENT) uses the same brand, so such data now serializes like other data instead of being refused. Server only; no client bytes.

  • eabc4c5: A bound <select value> marks its matching <option selected> while the server renders, not in a pass over the finished html (#4891). The old pass was switched on for the whole process by a module-scope ssrSelectValues() call in any module that had a bound select, so every render after it scanned its full html and every streamed fragment, even with no select in it. Both compilers now read the select's value once ahead of its children (ssrSelect) and keep it on a transparent owner the select's children render under (ssrIn). Each compiled server <option> asks ssrOption whether it is selected. Options a <For>, a component or a later async pass renders inside the select are matched too, and a select whose options stream in after a <Loading> inside it is no longer left unmarked. ssrSelectValues and resolveSSRSelectValues are removed. A multiple select's bound value is matched as the client assigns it, entry by entry from an array, with no comma split. An option whose text content is markup (no value attribute) is not matched.

  • 8a6de0f: Dev builds now warn with STRICT_READ_UNTRACKED (#1742) when an element's event handler expression reads reactive state, e.g. onClick={selected() ? a : b} or a props.onClick the parent passed a reactive expression. A handler is bound once, where its element is created, so such a read is not meant to be reactive; the warning says to read the value inside the handler function instead. A pending async read there throws PENDING_ASYNC_UNTRACKED_READ, as in a component body. The read still tracks, so dev re-mounts on a change exactly as production does.

    Production output is unchanged byte for byte. In dev DOM output both compilers wrap each handler expression that can read reactive state (anything but an identifier, an inline function or a literal), including the [handler, data] form's bound values on delegated events, in a new strictHandler(() => expr) helper from @solidjs/web. It reads expr through strictReadTracked, a dev-only strict-read window that keeps tracking (internal, from @solidjs/signals via solid-js). No production-compiled module imports either, so they tree-shake away.

  • 7b405e9: Add setTrustedTypesPolicy(policy), so Solid renders on pages that enforce require-trusted-types-for 'script'. The app passes a Trusted Types policy it created, and the runtime assigns the TrustedHTML that policy returns, for compiled template markup, for the server HTML the frames client parses, and for the html tagged template's text decoding. On the server it is a no-op, so an isomorphic entry can call it. Nothing changes for apps that do not call it.

  • b3731b0: A text value that shares its parent with other children (<p>Count: {n()}</p>) no longer reads the text node's .data before each write. The node remembers the last value written and later updates compare against that, which takes a signal-driven text update from about 0.59 µs to about 0.35 µs. The first write to a node still compares with the DOM. One behavior difference: if something outside Solid rewrites such a text node and the value then re-evaluates to what Solid last wrote, the text is no longer written back.

  • Updated dependencies [cf839ea]

  • Updated dependencies [8858189]

  • Updated dependencies [c927463]

  • Updated dependencies [4ba1731]

  • Updated dependencies [c50abb6]

  • Updated dependencies [e76279f]

  • Updated dependencies [32b4294]

  • Updated dependencies [9c917ea]

  • Updated dependencies [7230c08]

  • Updated dependencies [c8c5eb1]

  • Updated dependencies [231a3d8]

  • Updated dependencies [8a6de0f]

    • solid-js@2.0.0-rc.22

Don't miss a new solid release

NewReleases is sending notifications on new releases.