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
_holeVNodes 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 withhole. -
Namespaces are inferred. An
svgormathelement switches its subtree to the SVG or MathML namespace (foreignObjectswitches back to HTML), so SVG works with the same helpers as HTML. -
Hydration claims instead of creating.
hydrateruns the same mount code, but each node claims the matching node a server render already put in the document (seewybthon.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 |
Functions:
| Name | Description |
|---|---|
render |
Render a tree into a container element. |
hydrate |
Adopt server-rendered HTML under |
mount |
Emit ops mounting a VNode (or string) under |
unmount |
Unmount |
patch |
Diff |
Root
¶
render
¶
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 |
required |
Returns:
| Type | Description |
|---|---|
Root
|
A |
hydrate
¶
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
|
required |
Returns:
| Type | Description |
|---|---|
Root
|
A |
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
|
ns
|
str | None
|
Namespace of the parent ( |
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
¶
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:
- The container is resolved to a kernel node id and registered as an
event-delegation root (
ROOTop). - The tree mounts under a fresh
Owner; component bodies run once, and holes and reactive props create render effects. 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¶
- Kernel: the op protocol and backends
- VNode: the data structure being diffed
- Concepts: Virtual DOM
- Concepts: Lifecycle and ownership