VDOM
wybthon.vdom¶
vdom
¶
Virtual DOM primitives, diffing, and rendering to real DOM elements.
This module is a thin compatibility shim that re-exports the public API
from the focused sub-modules. Existing code that imports from
wybthon.vdom continues to work unchanged.
Re-exports:
wybthon.vnode:VNode,h,Fragment,memo,dynamic,is_getter.wybthon.reconciler:render,mount,unmount,patch.wybthon.error_boundary:ErrorBoundary.wybthon.suspense:Suspense.wybthon.portal:create_portal.wybthon.props: prop-diffing helpers (is_event_prop, etc.).
Classes:
| Name | Description |
|---|---|
VNode |
Virtual node representing an element, text, component, or reactive hole. |
Functions:
| Name | Description |
|---|---|
ErrorBoundary |
Catch render errors in children and display a fallback. |
create_portal |
Render children into a different DOM container. |
is_event_prop |
Return True if |
mount |
Mount a VNode (or string) into |
patch |
Diff |
render |
Render a VNode tree into a container element. |
unmount |
Unmount |
Suspense |
Render a fallback while one or more resources are loading. |
Fragment |
Group multiple children without adding an extra DOM wrapper element. |
dynamic |
Create a reactive-hole VNode that re-evaluates |
h |
Create a VNode from a tag, props, and children. |
is_getter |
Return True when |
memo |
Wrap a function component to skip re-mounts when its props are unchanged. |
VNode
¶
VNode(tag: Optional[Union[str, Callable[..., Any]]], props: Optional[PropsDict] = None, children: Optional[List[ChildType]] = None, key: Optional[Union[str, int]] = None)
Virtual node representing an element, text, component, or reactive hole.
Uses __slots__ for a compact memory layout and faster attribute
access, which is meaningful when authoring large lists. Internal
attributes (el, subtree, render_effect, component_ctx,
_frag_end) are populated by the reconciler when the VNode is
mounted.
Attributes:
| Name | Type | Description |
|---|---|---|
tag |
Element tag name ( |
|
props |
Mapping of prop names to values. Event handlers, attributes, and reactive accessors all live here. |
|
children |
List of child |
|
key |
Optional stable identity used for keyed list reconciliation. |
ErrorBoundary
¶
Catch render errors in children and display a fallback.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
props
|
Any
|
The component's props with the following keys:
|
required |
Returns:
| Type | Description |
|---|---|
Any
|
A reactive |
Any
|
fallback whenever a child raises. |
create_portal
¶
Render children into a different DOM container.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
children
|
Union[VNode, List[VNode]]
|
A single |
required |
container
|
Any
|
An |
required |
Returns:
| Type | Description |
|---|---|
VNode
|
A |
VNode
|
|
VNode
|
component's reactive scope (signals, context, and lifecycle |
VNode
|
hooks still apply). |
is_event_prop
¶
mount
¶
Mount a VNode (or string) into container, returning its DOM element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vnode
|
Union[VNode, str]
|
The VNode to mount. Strings are coerced to text VNodes. |
required |
container
|
Element
|
The parent element wrapper. |
required |
anchor
|
Any
|
Optional sibling DOM node to insert before. When |
None
|
Returns:
| Type | Description |
|---|---|
Element
|
The mounted |
patch
¶
Diff old against new and apply minimal DOM changes inside container.
Same-type VNodes are patched in place (props and children diffed); different types are unmounted and remounted at the same anchor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
old
|
Optional[VNode]
|
The previously-rendered VNode, or |
required |
new
|
VNode
|
The new VNode to render. |
required |
container
|
Element
|
The parent element wrapper. |
required |
render
¶
Render a VNode tree into a container element.
Subsequent calls with the same container patch the existing tree
in place; only the differences are applied. Pass None (via the
internal API) to unmount.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vnode
|
VNode
|
The root VNode to render. |
required |
container
|
Union[Element, str]
|
An |
required |
Returns:
| Type | Description |
|---|---|
Element
|
The wrapped container |
Element
|
retaining a reference to the mount point. |
unmount
¶
unmount(vnode: VNode) -> None
Unmount vnode, disposing its effects, ownership scope, and DOM.
Calls cleanup on the owning component context (if any), removes
delegated event handlers, runs on_cleanup callbacks, and detaches
the underlying DOM node from its parent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vnode
|
VNode
|
The VNode to tear down. Safe to call on already-unmounted nodes (becomes a no-op). |
required |
Suspense
¶
Render a fallback while one or more resources are loading.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
props
|
Any
|
The component's props with the following keys:
|
required |
Returns:
| Type | Description |
|---|---|
Any
|
A reactive |
Any
|
between fallback and children. |
Fragment
¶
Group multiple children without adding an extra DOM wrapper element.
Fragments use empty comment nodes as start/end markers and mount their
children directly into the parent container. This avoids extra
elements that would pollute selectors like :first-child or affect
layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
Any
|
Either a sequence of children ( |
()
|
Returns:
| Type | Description |
|---|---|
VNode
|
A |
dynamic
¶
Create a reactive-hole VNode that re-evaluates getter on dependency changes.
This is the explicit form of the same machinery that wraps callable
children automatically. Use it when you want to be explicit about
which child is dynamic, or to attach a stable key for keyed reuse
inside a fragment.
The getter may return a VNode, a str, a list of either, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
getter
|
Callable[[], Any]
|
Zero-arg callable evaluated inside its own effect. Any signal reads inside the getter become dependencies that trigger re-evaluation. |
required |
key
|
Optional[Union[str, int]]
|
Optional stable identity used by keyed reconciliation. |
None
|
Returns:
| Type | Description |
|---|---|
VNode
|
A |
h
¶
h(tag: Optional[Union[str, Callable[..., Any]]], props: Optional[PropsDict] = None, *children: Any) -> VNode
Create a VNode from a tag, props, and children.
This is the low-level VNode constructor used everywhere. For
common HTML tags, prefer the helpers in
wybthon.html (div, span, button, …).
Callable children (zero-argument getters) are passed through
unchanged; normalize_children wraps them as _dynamic VNodes when
the parent element mounts. Components receive their children verbatim
via the children prop so they can decide how to render them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
Optional[Union[str, Callable[..., Any]]]
|
An HTML tag name ( |
required |
props
|
Optional[PropsDict]
|
Mapping of prop names to values. May be |
None
|
*children
|
Any
|
Children to attach. Lists/tuples are flattened. |
()
|
Returns:
| Type | Description |
|---|---|
VNode
|
A new |
is_getter
¶
Return True when value is a zero-arg callable suitable for a reactive hole.
The check excludes:
VNodeinstances- Classes (
isinstance(value, type)) - Components and providers (marked with
_wyb_component/_wyb_provider) Refobjects (have acurrentattribute)- Callables that require positional arguments (e.g. event handlers taking an event object)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Any value, typically a child or prop value being normalized. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
memo
¶
memo(component: Callable[..., Any], are_props_equal: Optional[Callable[[PropsDict, PropsDict], bool]] = None) -> Callable[..., Any]
Wrap a function component to skip re-mounts when its props are unchanged.
Because Wybthon component bodies run once, memo is only useful
when you want to skip re-mounting on a prop change (for example,
components with an expensive setup phase). Most ordinary components
do not need it; fine-grained holes already minimise DOM work.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
component
|
Callable[..., Any]
|
The component callable to memoize. |
required |
are_props_equal
|
Optional[Callable[[PropsDict, PropsDict], bool]]
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
Callable[..., Any]
|
A wrapped component callable. Identity is preserved across |
Callable[..., Any]
|
renders so the reconciler can detect "same component, same |
Callable[..., Any]
|
props". |
The VDOM system is implemented as a set of focused sub-modules. The
wybthon.vdom module re-exports all public names for convenience:
| Module | Responsibility |
|---|---|
wybthon.vnode |
VNode, h(), Fragment, memo() |
wybthon.reconciler |
render(), mount(), unmount(), patch() |
wybthon.props |
DOM prop application, style/event/dataset diffing |
wybthon.error_boundary |
ErrorBoundary component |
wybthon.suspense |
Suspense component |
wybthon.portal |
create_portal() |
You can import from either wybthon.vdom or the specific sub-module:
from wybthon.vdom import h, render # re-export hub
from wybthon.vnode import h # direct import
from wybthon.reconciler import render # direct import
Public API¶
VNodeh(tag, props=None, *children) -> VNodeFragment(*children). Groups children without a wrapper element; the reconciler uses invisible comment markers and mounts children in the parent (nodisplay:contentsspan).render(vnode, container) -> Elementdynamic(getter, *, key=None) -> VNode. Wrap a zero-arg callable as a reactive hole (see Primitives, Reactive Holes). Callable children passed toh()are auto-wrapped via this helper.is_getter(value) -> bool. Predicate the reconciler uses to decide whether a callable child or prop value is a reactive hole. Useful when authoring components or helpers that need to detect getters explicitly.ErrorBoundarycomponentSuspensecomponentmemo(component, are_props_equal=None). Memoize a function component.create_portal(children, container). Render children into a different DOM container.
Keyed children and diffing¶
h(tag, {"key": key}, ...)assigns a stable identity to a child.- During reconciliation, children are matched by key first, then by type for unkeyed nodes.
- Reorders are applied with minimal DOM moves using a right-to-left pass with a moving anchor.
- Unmatched old nodes are unmounted; unmatched new nodes are mounted at the correct anchor.
Text nodes (fast-path)¶
- When both the old and new nodes are text, the same DOM text node is reused and only
nodeValueis updated. - Lists of plain text children are reconciled efficiently; typically the framework updates text in-place and minimizes DOM moves.
- Example:
from wybthon import h, render
from wybthon.dom import Element
root = Element(node=document.createElement("div"))
render(h("div", {}, "hello"), root)
render(h("div", {}, "world"), root) # updates the same text node
Suspense¶
Suspense renders a fallback while one or more resources are loading.
- Props:
resourceorresources=[...]fallback– VNode/str/callablekeep_previous=False– keep children visible during subsequent reloads
ErrorBoundary¶
ErrorBoundary catches render errors from its subtree and renders a fallback.
- Props:
fallback– VNode/str/callable. When callable, it is invoked asfallback(error, reset); if the callable only accepts one argument, it is invoked asfallback(error).on_error– optional callback called with the thrown error when the boundary captures it.reset_key– any value; when this value changes, the boundary automatically resets (clears the error) on the next render.-
reset_keys– list/tuple of values; when the tuple of values changes, the boundary automatically resets. -
Methods:
-
reset()– imperative method to clear the current error and attempt re-rendering children. -
Notes:
- If the fallback callable throws, a simple text node "Error rendering fallback" is shown.
- When not in an error state, the boundary renders its
childrenwrapped in aFragment.
Prop semantics (style, dataset, value, checked)¶
style: pass a dict of CSS properties using camelCase keys. Keys are converted to kebab-case and applied viastyle.setProperty. On updates, keys that are absent in the new dict are removed withstyle.removeProperty. PassingNoneor a non-dict clears previously set style keys.
h("div", {"style": {"backgroundColor": "red", "fontSize": 14}})
# → sets background-color: red; font-size: 14
dataset: pass a dict; entries map todata-*attributes. On updates, keys not present are removed. PassingNoneor a non-dict clears previously setdata-*attributes.
-
value: for form controls, the DOMvalueproperty is set (falling back to thevalueattribute if needed).Nonebecomes "". Removing thevalueprop resets it to "". -
checked: for checkboxes/radios, the DOMcheckedproperty is set when available (falling back to thecheckedattribute). Removing thecheckedprop clears it toFalse.
memo¶
memo(component, are_props_equal=None) wraps a function component to
skip re-mounting when props are unchanged.
- By default, uses shallow identity comparison (
is) on each prop value. - Pass a custom
are_props_equal(old_props, new_props) -> boolfor deeper comparison.
from wybthon import component, memo, p
@component
def ExpensiveList(items=None):
its = items() or []
return p("Items: ", str(len(its)))
MemoList = memo(ExpensiveList)
Because Wybthon components run once and then update via reactive
holes, memo is only useful when you want to skip re-mounting on
prop changes (for example, an expensive setup phase). Most components
do not need it.
create_portal¶
create_portal(children, container) renders children into a different DOM container.
- children: a single VNode or a list of VNodes.
- container: an
Elementor CSS selector string.
from wybthon import create_portal, h
portal = create_portal(h("div", {}, "Modal content"), "#modal-root")
Development mode¶
Wybthon includes a development mode (DEV_MODE = True by default) that provides
clear error messages to stderr when something goes wrong during rendering, event
handling, or lifecycle hooks. Errors include component names and tracebacks.