Kernel
wybthon.kernel¶
kernel
¶
Batched DOM command buffer and rendering backends.
This module is the single point of contact between Wybthon's renderer and the real DOM. Instead of calling DOM APIs directly (each call crosses the Python-to-JS bridge in Pyodide), the reconciler and prop appliers emit compact operations into a buffer. At well-defined commit points (end of a render, end of an effect flush) the whole buffer is serialized once and handed to a small JavaScript kernel that applies every operation natively. A mount of a 1,000-row table becomes one bridge crossing instead of tens of thousands.
Core concepts:
- Node handles. DOM nodes are referred to by integer ids allocated
on the Python side. The kernel keeps an
id -> Noderegistry, so noJsProxyobjects flow through the hot path. - Ops. Each operation is a small tuple,
(opcode, ...args), serialized as a JSON array. See theOP_*constants. - Backends.
BrowserBackenddrives the real DOM through the embedded JS kernel.PythonBackendis a reference interpreter that applies the same ops to any DOM-like stub document; it backs the unit tests and the stubbed benchmark so both exercise the exact protocol the browser sees. - Hydration.
CLAIM_*ops adopt server-rendered nodes in document order instead of creating them. Claims are tolerant: a node that doesn't match is created in place, andHYDRATE_ENDremoves server nodes nobody claimed. - Events. Event delegation lives in the kernel: one native listener
per event type walks the ancestor chain natively and calls into
Python once for the matching bubbling route with a JSON payload. See
wybthon.eventsfor the Python half.
Application code never imports this module directly; it's plumbing for
the reconciler, wybthon._dom_props, and wybthon.events.
Classes:
| Name | Description |
|---|---|
BrowserBackend |
Backend that drives the real DOM through the embedded JS kernel. |
PythonBackend |
Reference op interpreter over a DOM-like stub document. |
Functions:
| Name | Description |
|---|---|
commit |
Flush all queued ops to the backend in one crossing. |
set_backend |
Install a rendering backend (tests pass a |
reset |
Test helper: clear the op buffer, id counters, and template registry. |
BrowserBackend
¶
Backend that drives the real DOM through the embedded JS kernel.
Created automatically on first use inside Pyodide. Every apply
serializes the op list to JSON and makes exactly one call across
the bridge.
Methods:
| Name | Description |
|---|---|
apply |
Serialize |
get_node |
Return the raw DOM node registered under |
adopt |
Return an existing handle or register the node under |
query |
Return the canonical handle of a selector match, or None. |
supports_html |
The browser can always parse template HTML. |
set_dispatcher |
Install the Python event dispatcher as the kernel's callback proxy. |
current_event |
Return the native event currently being dispatched, or |
take_state |
Remove and return the text of the server state script under |
replay |
Replay input the bootstrap recorded before hydration. |
adopt
¶
Return an existing handle or register the node under node_id.
query
¶
Return the canonical handle of a selector match, or None.
set_dispatcher
¶
Install the Python event dispatcher as the kernel's callback proxy.
take_state
¶
Remove and return the text of the server state script under node_id.
PythonBackend
¶
PythonBackend(document: Any)
Reference op interpreter over a DOM-like stub document.
Mirrors the JS kernel's semantics against plain Python objects that
quack like DOM nodes (the test suite's StubNode and the benchmark
runner's _Node). Unit tests and the stubbed benchmark run the real
wire protocol through this class, so protocol bugs surface without
a browser.
Methods:
| Name | Description |
|---|---|
apply |
Interpret a batch of ops against the stub document. |
get_node |
Return the stub node registered under |
adopt |
Return an existing handle or register this stub node. |
query |
Return the canonical handle of a selector match, or None. |
supports_html |
Whether the stub document parses |
set_dispatcher |
Install the Python event dispatcher used by |
current_event |
Return the raw event passed to the in-flight |
take_state |
Remove and return the text of the server state script under |
replay |
Replay |
roots |
Return the registered delegation roots (test helper). |
dispatch |
Simulate a bubbling native event dispatch for tests. |
query
¶
Return the canonical handle of a selector match, or None.
set_dispatcher
¶
Install the Python event dispatcher used by dispatch.
current_event
¶
current_event() -> Any
Return the raw event passed to the in-flight dispatch, or None.
take_state
¶
Remove and return the text of the server state script under node_id.
dispatch
¶
dispatch(event_type: str, target: Any, raw_event: Any = None, payload: Optional[dict] = None) -> None
Simulate a bubbling native event dispatch for tests.
Walks the stub-node ancestor chain from target, invoking the
Python dispatcher for every registered (node, event_type)
handler, exactly like the JS kernel's root listener.
register_template
¶
Register shape's skeleton with the backend and return its template id.
The registration op travels in the same batch as the clone that
needs it, so no extra bridge crossing occurs. The oldest template is
retired once more than _TEMPLATE_LIMIT are live; its shape
registers again on its next mount.
commit
¶
Flush all queued ops to the backend in one crossing.
No-op when the buffer is empty. Safe to call at any time; the
renderer calls it at the end of render, at the end of every
effect flush, and before any synchronous DOM read.
stats
¶
Return node, template, listener, root, and hydration-mismatch counts.
take_state
¶
Remove and return the serialized server state inside node_id, if any.
Server renders end with a <script type="application/json"
data-wyb-state> element. Commits pending ops first so the node
exists.
replay_events
¶
replay_events() -> int
Replay input recorded before hydration through the delegated handlers.
Returns the number of events replayed.
get_node
¶
Return the raw DOM node for node_id, committing pending ops first.
query
¶
Resolve a CSS selector to a node id, or None when nothing matches.
Commits pending ops first so the selector can match nodes created earlier in the same logical update.
supports_html
¶
supports_html() -> bool
Return whether the backend can parse templates (OP_REGISTER_TPL).
current_event
¶
current_event() -> Any
Return the native event being dispatched right now, or None.
Only valid synchronously inside an event handler; used as the
escape hatch behind DomEvent.raw.
set_event_dispatcher
¶
Install the Python-side event dispatcher (called by wybthon.events).
set_backend
¶
set_backend(backend: Any) -> None
Install a rendering backend (tests pass a PythonBackend).
Clears the template registry: a fresh backend has no registered skeletons, so they must be re-sent on next use.
reset
¶
Test helper: clear the op buffer, id counters, and template registry.
What's in this module¶
kernel is the single point of contact between the renderer and the
real DOM. The reconciler, the compiled template mounts, the prop
appliers, and the event system emit compact ops (JSON-serializable
tuples against integer node ids) into a buffer; commit() hands the
whole buffer to the active backend in one Python-to-JS bridge crossing.
The browser half lives in _kernel.js, shipped with the package.
Application code never imports this module; it matters when you're
writing low-level tests against the stub backend or debugging the wire
protocol. For component tests, use wybthon.testing.
| Name | Description |
|---|---|
commit |
Flush every queued op to the backend in one crossing (no-op when empty). |
BrowserBackend |
Drives the real DOM through the JS kernel; created automatically in Pyodide. |
PythonBackend |
Reference interpreter applying the same ops to a DOM-like stub document (used by wybthon.testing, the unit tests, and the stubbed benchmark). |
set_backend |
Install a backend (tests pass a PythonBackend). |
reset |
Test helper: clear the op buffer, id counters, and template registry, optionally installing a backend. |
Two more module-level hooks matter to tests. register_template(shape)
registers the static skeleton of a compiled html
template in the same batch as its first clone. html_templates (True, False, or None for
"ask the backend") gates template mounting; set it to False to force
per-node commands.
Wire protocol¶
Each op is a JSON array whose first element is the opcode. Node ids are
allocated on the Python side, so no JsProxy objects flow through the
hot path; None anchors mean "append".
| Op | Payload | Effect |
|---|---|---|
CREATE_ELEMENT |
id, tag |
document.createElement |
CREATE_ELEMENT_NS |
id, namespace, tag |
document.createElementNS (SVG, MathML) |
CREATE_TEXT |
id, text |
document.createTextNode |
CREATE_COMMENT |
id[, data] |
Comment marker (fragment and hole anchors); Loading boundaries carry a key in data during server rendering and hydration |
REGISTER_TPL |
tpl_id, html, count, [text offsets], [[offset, event_type], ...] |
Parse a template's or shape's skeleton once via <template>, and record its text slots and delegated listeners |
CLONE |
first_id, tpl_id, parent_id, anchor_id, *texts |
Clone the prototype, register a dense id block in pre-order, fill the text slots, mark the delegated listeners, and insert before the anchor |
INSERT |
parent_id, id, anchor_id |
insertBefore (None anchor appends) |
REMOVE |
id |
Detach from the parent; the node stays registered |
MOVE_RANGE |
parent_id, first_id, last_id, anchor_id |
Move a contiguous mounted range |
DISPOSE_RANGE |
first_id, last_id |
Remove a contiguous range of siblings and release every registered node inside it, walking the removed nodes natively |
DISPOSE |
id |
Remove one node and release every registered node inside it |
HOLE_TEXT |
id, text |
Reuse a hole's placeholder as a visible text node |
RELEASE_TPL |
tpl_id |
Evict a native template prototype |
SET_TEXT |
id, text |
nodeValue assignment |
SET_ATTR |
id, name, value |
setAttribute, or removeAttribute when value is None |
SET_PROP |
id, name, value |
DOM property assignment (value, checked, selectedValues, innerHTML) |
SET_STYLE |
id, decls |
style.setProperty / removeProperty per kebab-case declaration |
LISTEN / UNLISTEN |
id, event_type[, options] |
Delegated bookkeeping for nodes outside a template, or a direct native listener with options; unlisten omits options |
RELEASE |
[ids] |
Drop registry entries and listener sets (used for a disposed root's container) |
ROOT / UNROOT |
id |
Start or stop delegating events from this container instead of document |
HYDRATE |
root_id |
Start claiming server-rendered nodes under root_id |
CLAIM_ELEMENT |
id, parent_id, tag, namespace |
Adopt the parent's next element, or create one in place on a mismatch |
CLAIM_TEXT |
id, parent_id, text |
Adopt the next text node, splitting text the HTML parser merged; empty text is created |
CLAIM_COMMENT |
id, parent_id, data |
Adopt the next comment; a keyed end marker (/...) resynchronizes by searching forward |
CLAIM_STATIC |
parent_id, end_marker |
Keep the server nodes before the marker as static DOM (a NoHydration region) |
HYDRATE_END |
none | Remove server nodes nobody claimed and stop claiming |
A row of the js-framework-benchmark table costs one command, a CLONE
that carries the row's text and its label's first value, when it's
written as an html template. Clearing a list
is one DISPOSE_RANGE. Unmounting never sends lists of every node id: the
kernel releases the removed nodes itself. See
Virtual DOM for how compiled
templates, precompiled mount functions, and lazy node ids fit
together.
hydrate emits the claim ops; see Server rendering. stats() reports node, template, listener, and root counts plus hydration_mismatches, and take_state() and replay_events() read the server state and replay input recorded before hydration.
Events travel the other way: the JS kernel installs one native listener per event type on each render root, walks the ancestor chain natively, and calls the Python dispatcher once for the matching bubbling route with a small JSON payload (see events).
from wybthon import kernel
# In a CPython test, install the stub backend over any DOM-like document
# (wybthon.testing and the test suite's conftest do this for you).
kernel.reset(kernel.PythonBackend(stub_document))
kernel.html_templates = False # optional: force per-node commands
See also¶
- Reconciler: emits the ops
- Events: the Python half of delegation
- Testing: the in-memory renderer built on
PythonBackend - Concepts: Virtual DOM
- Guides: Testing