Documentation
¶
Overview ¶
Package vdom implements a virtual DOM tree with diffing and patching. It handles template parsing, tree resolution, diffing, and patch generation. The godom runtime uses this package for its rendering pipeline.
Index ¶
- Constants
- func BuildExprEnv(ctx *ResolveContext) map[string]any
- func ComputeDescendants(n Node) int
- func CopyVars(vars map[string]any) map[string]any
- func DeepCopyJSON(v any) any
- func IsTruthy(val any) bool
- func IsValidIdentifier(name string) bool
- func MarkRemoved(n Node)
- func MergeTree(dst, src Node) map[int]int
- func ParseForExpr(expr string) (item, index, list string)
- func ParseMapAccess(expr string) (field, key string, ok bool)
- func ParseMethodCall(expr string) (method string, args []string)
- func ResolveExpr(exprStr string, ctx *ResolveContext) any
- type Binding
- type Directive
- type ElementNode
- type EventHandler
- type EventOptions
- type Facts
- type FactsDiff
- type IDCounter
- type InputBinding
- type KeyedChild
- type KeyedElementNode
- type LazyNode
- type NSAttr
- type Node
- type NodeBase
- type Patch
- type PatchAppendData
- type PatchFactsData
- type PatchLazyData
- type PatchPluginData
- type PatchRedrawData
- type PatchRemoveData
- type PatchRemoveLastData
- type PatchReorderData
- type PatchTextData
- type PluginNode
- type ReorderInsert
- type ReorderRemove
- type ResolveContext
- type TemplateNode
- type TextNode
- type TextPart
Constants ¶
const ( NodeText = iota // plain text content NodeElement // HTML/SVG element with tag, facts, children NodeKeyed // element with keyed children (for efficient list diffing) NodePlugin // opaque JS-managed node (plugin escape hatch) NodeLazy // deferred computation, skipped if inputs unchanged )
Node type constants identify each Node variant.
const ( PatchRedraw = iota // replace entire node PatchText // update text content PatchFacts // update properties/attributes/styles/events PatchAppend // add children at end PatchRemoveLast // remove N children from end PatchRemove // remove specific child (keyed) PatchReorder // reorder keyed children (inserts/removes/moves) PatchPlugin // plugin data changed PatchLazy // wrapper for patches inside a lazy node )
Patch operation constants.
Variables ¶
This section is empty.
Functions ¶
func BuildExprEnv ¶
func BuildExprEnv(ctx *ResolveContext) map[string]any
BuildExprEnv returns the expr-lang environment for the current context. The base env (struct fields + methods) is built once per render and cached. Loop variables are merged on top each call.
func ComputeDescendants ¶
ComputeDescendants recursively calculates and caches descendant counts. Must be called after building a tree and before diffing.
func DeepCopyJSON ¶
DeepCopyJSON deep-copies a value by JSON round-tripping.
func IsValidIdentifier ¶
IsValidIdentifier checks that a name looks like a Go/JS identifier: letters, digits, underscores; cannot start with a digit.
func MarkRemoved ¶
func MarkRemoved(n Node)
MarkRemoved recursively marks a node and all its descendants as removed.
func MergeTree ¶
MergeTree updates dst in place with data from src, keeping dst's node IDs. Structurally matching nodes (same type, same tag) get their data updated. Non-matching nodes at the same position are replaced with src nodes.
Returns a map from src node IDs → dst node IDs for every position where dst's ID was kept (canMerge=true and IDs differ). Callers use this to remap bindings that reference src IDs to the IDs the merged tree has.
Call this after Diff(dst, src) to bring dst in sync with what the browser will have after patches are applied. dst is never replaced — it is the long-lived tree that persists across renders.
func ParseForExpr ¶
ParseForExpr parses "todo in Todos" or "todo, i in Todos".
func ParseMapAccess ¶
ParseMapAccess parses "Field[key]" into ("Field", "key", true). Returns ("", "", false) if the expression doesn't match bracket syntax.
func ParseMethodCall ¶
ParseMethodCall parses "Save", "Toggle(i)", "Remove(i, todo.ID)".
func ResolveExpr ¶
func ResolveExpr(exprStr string, ctx *ResolveContext) any
ResolveExpr resolves an expression string against the context. Simple field/variable access uses direct reflection (fast path). Complex expressions (comparisons, operators, function calls) use expr-lang.
Types ¶
type Binding ¶
type Binding struct {
NodeID int
Kind string // "style", "prop", "attr", "text"
Prop string // property/style/attr name (empty for text)
Expr string // original expression (e.g., "Inputs[first]") — used by g-bind to write back
}
Binding records a dependency: "field X affects node Y's property Z." Used for surgical updates — when a field changes, only the bound nodes are patched.
type Directive ¶
type Directive struct {
Type string // "text", "html", "bind", "value", "checked", "if", "show", "hide", "class", "attr", "style", "prop",
// "click", "keydown", "mousedown", "mousemove", "mouseup", "wheel", "scroll", "drop",
// "draggable", "dropzone"
Name string // modifier name: class name, attr name, style property, key filter, etc.
Expr string // expression: field name, method call, etc.
}
Directive represents a single g-* directive on an element.
type ElementNode ¶
type ElementNode struct {
NodeBase
Tag string // e.g. "div", "span", "path"
Namespace string // "" for HTML, "http://www.w3.org/2000/svg" for SVG
Facts Facts
Children []Node
}
ElementNode represents an HTML or SVG element.
func (*ElementNode) AppendChild ¶
func (n *ElementNode) AppendChild(child Node)
AppendChild adds a child node.
func (*ElementNode) NodeType ¶
func (n *ElementNode) NodeType() int
func (*ElementNode) RemoveChild ¶
func (n *ElementNode) RemoveChild(index int) bool
RemoveChild removes the child at the given index. Returns false if out of bounds.
func (*ElementNode) RemoveChildByID ¶
func (n *ElementNode) RemoveChildByID(id int) bool
RemoveChildByID removes the first child with the given node ID. Returns false if not found.
func (*ElementNode) ReplaceChild ¶
func (n *ElementNode) ReplaceChild(index int, child Node) bool
ReplaceChild replaces the child at the given index. Returns false if out of bounds.
type EventHandler ¶
type EventHandler struct {
Handler string // method name on Go struct
Args []any // pre-resolved arguments
Scope string // "forGID:index" for child component routing
Options EventOptions
}
EventHandler describes an event listener that routes to a Go method.
type EventOptions ¶
type EventOptions struct {
StopPropagation bool
PreventDefault bool
Key string // key filter for keydown events
}
EventOptions controls event propagation behavior.
type Facts ¶
type Facts struct {
Props map[string]any // DOM properties: id, className, value, checked, ...
Attrs map[string]string // HTML attributes: data-*, aria-*, custom
AttrsNS map[string]NSAttr // namespaced attributes: xlink:href, xml:lang
Styles map[string]string // CSS properties: background-color, width, ...
Events map[string]EventHandler // event listeners: click, input, keydown, ...
}
Facts holds all the "attributes" of an element, categorized by type.
type FactsDiff ¶
type FactsDiff struct {
Props map[string]any // changed/added/removed properties
Attrs map[string]string // changed/added/removed attributes ("" = remove)
AttrsNS map[string]NSAttr // changed/added/removed namespaced attributes
Styles map[string]string // changed/added/removed styles ("" = remove)
Events map[string]*EventHandler // changed/added events (nil = remove)
}
FactsDiff represents changes between two Facts.
type IDCounter ¶
type IDCounter struct {
Seq int
// contains filtered or unexported fields
}
IDCounter assigns monotonically increasing node IDs. It must persist across renders (never reset) so that existing IDs in the bridge's node map remain valid. Thread-safe — shared across all components.
type InputBinding ¶
type InputBinding struct {
Field string // struct field name (binding key)
Expr string // original expression (e.g., "Inputs[first]")
Prop string // "value" or "checked"
}
InputBinding is the reverse lookup: nodeID → field info for input nodes.
type KeyedChild ¶
KeyedChild pairs a stable key with a child node.
type KeyedElementNode ¶
type KeyedElementNode struct {
NodeBase
Tag string
Namespace string
Facts Facts
Children []KeyedChild
}
KeyedElementNode is an element whose children have stable keys.
func (*KeyedElementNode) NodeType ¶
func (n *KeyedElementNode) NodeType() int
type LazyNode ¶
type LazyNode struct {
NodeBase
Func any // the view function
Args []any // arguments checked by reference equality
Cached Node // previously computed result (nil on first render)
}
LazyNode defers tree construction until diffing time.
func (*LazyNode) DescendantsCount ¶
type Node ¶
Node is the interface implemented by all virtual DOM node types.
func FindNodeByID ¶
FindNodeByID searches the tree for a node with the given ID.
func MergeAdjacentText ¶
MergeAdjacentText collapses consecutive TextNode entries into one and removes empty TextNodes.
func ResolveTemplateNode ¶
func ResolveTemplateNode(t *TemplateNode, ctx *ResolveContext) []Node
ResolveTemplateNode resolves a single template node into zero or more Nodes.
func ResolveTree ¶
func ResolveTree(templates []*TemplateNode, ctx *ResolveContext) []Node
ResolveTree resolves a list of template nodes into concrete Nodes.
type NodeBase ¶
type NodeBase struct {
ID int // stable identity, assigned by Go, used to address the node in the bridge
Descendants int // cached count, set by ComputeDescendants
Removed bool // true after the node has been removed from the live tree
}
NodeBase holds fields common to all node types. Embed this in every concrete node.
func (*NodeBase) DescendantsCount ¶
type Patch ¶
type Patch struct {
Type int // one of the Patch* constants
NodeID int // ID of the target node in the OLD tree (what the bridge knows)
Data any // type-specific payload (see below)
}
Patch describes a single DOM mutation produced by the diff algorithm.
type PatchAppendData ¶
type PatchAppendData struct {
Nodes []Node
}
PatchAppendData carries new children to append.
type PatchFactsData ¶
type PatchFactsData struct {
Diff FactsDiff
}
PatchFactsData carries the diff between old and new facts.
type PatchLazyData ¶
type PatchLazyData struct {
Patches []Patch
}
PatchLazyData wraps patches that apply inside a lazy node's cached subtree.
type PatchPluginData ¶
type PatchPluginData struct {
Data any
}
PatchPluginData carries new data for a plugin node.
type PatchRedrawData ¶
type PatchRedrawData struct {
Node Node
}
PatchRedrawData carries the new node to render from scratch.
type PatchRemoveData ¶
PatchRemoveData carries info for removing a specific keyed child.
type PatchRemoveLastData ¶
type PatchRemoveLastData struct {
Count int
}
PatchRemoveLastData carries the number of children to remove from the end.
type PatchReorderData ¶
type PatchReorderData struct {
Inserts []ReorderInsert
Removes []ReorderRemove
Patches []Patch
}
PatchReorderData carries the keyed reorder operation.
type PatchTextData ¶
type PatchTextData struct {
Text string
}
PatchTextData carries the new text content.
type PluginNode ¶
type PluginNode struct {
NodeBase
Tag string // host element tag, e.g. "canvas", "div"
Name string // plugin name, e.g. "chart"
Facts Facts
Data any // JSON-serializable data passed to JS plugin
}
PluginNode is an opaque node managed by a JS plugin.
func (*PluginNode) NodeType ¶
func (n *PluginNode) NodeType() int
type ReorderInsert ¶
ReorderInsert describes a node to insert at a given position.
type ReorderRemove ¶
ReorderRemove describes a node to remove (or move) during reorder.
type ResolveContext ¶
type ResolveContext struct {
State reflect.Value // the component struct (or pointer to it)
Vars map[string]any // loop variables: {todo: item, i: index}
IDs *IDCounter // node ID allocator (must persist across renders)
Bindings map[string][]Binding // field name → bindings (built during resolve)
// Reverse lookup: nodeID → field for input bindings (g-bind, g-value, g-checked)
InputBindings map[int]InputBinding
// Unbound input support
UnboundValues map[string]any // stableKey → stored value (passed in from component.Info)
NodeStableIDs map[int]string // nodeID → stableKey (built during resolve, read by server)
ForIndices []int // current g-for loop index stack (for composite stable keys)
// ExtraEnv holds engine-provided expression functions that are not struct
// fields or methods — e.g. the task-state bindings Busy/Progress/Err/Crashed.
// They are merged into the base env with lower precedence than struct
// members (a struct field/method of the same name wins, for back-compat).
ExtraEnv map[string]any
// contains filtered or unexported fields
}
ResolveContext holds the state and loop variables available during tree resolution.
type TemplateNode ¶
type TemplateNode struct {
// For element nodes
Tag string
Namespace string
Attrs []html.Attribute // static HTML attributes (non-directive)
// Directives (extracted from g-* attributes)
Directives []Directive
// Children (for elements) or nil (for text)
Children []*TemplateNode
// For text nodes
IsText bool
TextParts []TextPart // static text + {expr} interpolations
// For g-for nodes
IsFor bool
ForItem string // loop variable name, e.g. "todo"
ForIndex string // index variable name, e.g. "i" (empty if unused)
ForList string // list field, e.g. "Todos"
ForKey string // key expression, e.g. "todo.ID" (empty = positional)
ForBody []*TemplateNode // template for each item
// For plugin nodes
IsPlugin bool
PluginName string // plugin name from g-plugin:name
PluginExpr string // data expression
// StableID is a UUID assigned at parse time to unbound form inputs.
// Used to preserve input values across tree rebuilds.
StableID string
}
TemplateNode represents one node in the parsed template tree.
func ParseTemplate ¶
func ParseTemplate(htmlStr string) ([]*TemplateNode, error)
ParseTemplate parses HTML into a template tree.