Skip to content

Reconciler

wybthon.reconciler

reconciler

Reconciliation engine: mounting, patching, and unmounting VNode trees.

Translates VNode trees into batched DOM operations. Nothing here touches the DOM directly: every mutation is emitted as a compact op against an integer node id (see wybthon.kernel), and the whole buffer is applied in one bridge crossing at commit time (the end of render, the DOM phase of every flush).

Mental model:

  • Components run once. A component body is invoked a single time during mount and its returned tree is mounted directly. Updates flow through reactive holes and prop bindings embedded in that tree, never by re-running the body.
  • Reactive holes are _hole VNodes whose expression runs inside a render effect; when its dependencies change, only that region is patched. Holes are created for every reactive expression in a child position and explicitly with hole.
  • Namespaces are inferred. An svg or math element switches its subtree to the SVG or MathML namespace (foreignObject switches back to HTML), so SVG works with the same helpers as HTML.

  • Hydration claims instead of creating. hydrate runs the same mount code, but each node claims the matching node a server render already put in the document (see wybthon.server).

Public surface: render and hydrate, plus the lower-level mount, unmount, and patch used by control-flow primitives.

Classes:

Name Description
Root

A mounted application root returned by render.

Functions:

Name Description
render

Render a tree into a container element.

hydrate

Adopt server-rendered HTML under container and make it interactive.

mount

Emit ops mounting a VNode (or string) under parent_id.

unmount

Unmount vnode: dispose its scopes and effects, then remove its DOM.

patch

Diff old against new and emit minimal DOM ops under parent_id.

Root

Root(container: Element, vnode: VNode, owner: Owner)

A mounted application root returned by render.

Attributes:

Name Type Description
container

The container Element.

vnode

The currently rendered root VNode.

Methods:

Name Description
dispose

Unmount the tree, dispose every reactive scope, and stop event delegation.

node_id property

node_id: int

Kernel node id of the container.

dispose

dispose() -> None

Unmount the tree, dispose every reactive scope, and stop event delegation.

render

render(vnode: Any, container: Element | str | int) -> Root

Render a tree into a container element.

Mounts vnode under container, commits every buffered DOM op in one bridge crossing, and registers the container as an event delegation root. Rendering into the same container again patches the existing tree in place.

Parameters:

Name Type Description Default
vnode Any

The root VNode (or a component call, string, list, or reactive expression; anything a component may return).

required
container Element | str | int

An Element, a CSS selector for an existing DOM node, or a kernel node id.

required

Returns:

Type Description
Root

A Root; call .dispose() to tear the app down.

Example
from wybthon import render
from wybthon.html import h1

root = render(h1("Hello, world!"), "#app")

hydrate

hydrate(vnode: Any, container: Element | str | int) -> Root

Adopt server-rendered HTML under container and make it interactive.

The counterpart of render for pages rendered by wybthon.server. The tree mounts exactly as render would mount it, except that each node claims the matching node the server already put in the document instead of creating a new one. Async values the server resolved come from the page's data-wyb-state script, so they're neither fetched again nor shown as loading. Input recorded by the production bootstrap before hydration is replayed afterward.

Mismatches never break the page: a node that doesn't match is created in place, server nodes nobody claimed are removed, and dev mode logs a warning.

Parameters:

Name Type Description Default
vnode Any

The same root view the server rendered.

required
container Element | str | int

The element holding the server-rendered markup: an Element, a CSS selector, or a kernel node id.

required

Returns:

Type Description
Root

A Root, as from render.

Example
from wybthon import hydrate

hydrate(App(), "#app")

mount

mount(vnode: VNode | str, parent_id: int, anchor_id: int | None = None, ns: str | None = None, final: bool = False) -> None

Emit ops mounting a VNode (or string) under parent_id.

When the VNode carries an owner_scope (set by list primitives for cached rows), mounting runs under that owner with tracking suspended, so the row's effects survive later list updates.

Parameters:

Name Type Description Default
vnode VNode | str

The VNode to mount. Strings are coerced to text VNodes.

required
parent_id int

Kernel id of the parent node.

required
anchor_id int | None

Optional sibling id to insert before (None appends).

None
ns str | None

Namespace of the parent (None for HTML).

None
final bool

The subtree will never be patched (a component's output). List rows are final too. A final template-mounted subtree releases its static VNodes once mounted.

False

unmount

unmount(vnode: VNode) -> None

Unmount vnode: dispose its scopes and effects, then remove its DOM.

Safe to call on already-unmounted nodes (a no-op).

patch

patch(old: VNode | None, new: VNode, parent_id: int, ns: str | None = None) -> None

Diff old against new and emit minimal DOM ops under parent_id.

Identical instances (old is new, e.g. cached list rows) are skipped. VNodes with the same type and key are patched in place; a different type or key is unmounted and remounted at the same position.

What's in this module

The reconciler turns VNode trees into batched DOM operations. It never touches the DOM directly: every mutation is an op against an integer node id (see kernel), and the whole buffer is applied in one bridge crossing at commit time. Components run once; updates flow through reactive holes and prop bindings, each patching only its own region. Element subtrees mount through compiled shapes: a generated mount function per shape and one fused CLONE command per mount (see Virtual DOM).

Name Description
render Mount a tree into a container (Element, CSS selector, or node id) and return a Root. Rendering into the same container again patches in place.
hydrate Adopt server-rendered HTML under a container instead of creating it; see Server rendering.
Root Handle returned by render and hydrate; .container, .vnode, .node_id, and .dispose().
mount Lower level: emit ops mounting a VNode under a parent id, optionally before an anchor.
patch Lower level: diff an old VNode against a new one and emit minimal ops.
unmount Lower level: dispose a VNode's scopes and effects, then remove its DOM with one native DISPOSE or DISPOSE_RANGE command.

render, hydrate, and Root are re-exported from wybthon; mount, patch, and unmount are for control-flow primitives and tests.

from wybthon import component, create_signal, div, h1, p, render


@component
def App():
    title, set_title = create_signal("Hello")
    return div(h1(title), p("Rendered once; the heading is a hole."))


root = render(App(), "#app")
# Tear everything down: unmounts the tree, disposes every reactive
# scope, and stops event delegation on the container.
root.dispose()

What happens inside render:

  1. The container is resolved to a kernel node id and registered as an event-delegation root (ROOT op).
  2. The tree mounts under a fresh Owner; component bodies run once, and holes and reactive props create render effects.
  3. flush() commits staged writes, runs effects, and sends the op buffer across the bridge once.

Patching matches VNodes by type and key: a different tag or key unmounts and remounts at the same position (so Leaf(key=user_id()) restarts its state when the id changes). Keyed children use an identity, key, then type match with a longest-increasing-subsequence move pass to keep DOM moves minimal. When a keyed list reuses none of its rows, the new rows mount as one splice and the old ones are removed with a single DISPOSE_RANGE. Errors raised during mount or in a hole route to the nearest Errored boundary.

See also