dom

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 11, 2026 License: MIT Imports: 1 Imported by: 0

README ΒΆ

tinywasm/dom

Project Badges

Ultra-minimal DOM & event toolkit for Go (TinyGo WASM-optimized).

tinywasm/dom provides a minimalist, WASM-optimized way to interact with the browser DOM in Go, avoiding the overhead of the standard library and syscall/js exposure. It is designed specifically for TinyGo applications where binary size and performance are critical.

πŸš€ Features

  • Elm-Inspired Architecture: Component-local state with explicit updates (Model β†’ Update β†’ View)
  • Fluent Builder API: Chainable methods for concise, readable code
  • Hybrid Rendering: Choose DSL for dynamic components or string HTML for static ones
  • TinyGo Optimized: Avoids heavy standard library packages to keep WASM binaries <500KB
  • Direct DOM Manipulation: No Virtual DOM overhead. You control the updates.
  • ID-Based Caching: Efficient element lookup and caching strategy
  • Memory Safe: Automatic event listener cleanup on Unmount
  • Lifecycle Hooks: OnMount, OnUpdate, OnUnmount for fine-grained control

πŸ“¦ Installation

go get github.com/tinywasm/dom

⚑ Quick Start

Example 1: Dynamic Component (Elm Pattern)

Counter component with state management:

//go:build wasm

package main

import (
	"github.com/tinywasm/dom"
	"github.com/tinywasm/fmt"
)

// Model (state)
type Counter struct {
	*dom.Element
	count int
}

// View (rendering)
func (c *Counter) Render() dom.Node {
	return dom.Div().
		Class("counter").
		Add(
			dom.Button().
				Text("-").
				OnClick(c.Decrement),
			dom.Span().
				Class("count").
				Text(fmt.Sprint(c.count)),
			dom.Button().
				Text("+").
				OnClick(c.Increment),
		).
		ToNode()
}

// Update (state mutations)
func (c *Counter) Increment(e dom.Event) {
	c.count++
	c.Update() // Explicit re-render
}

func (c *Counter) Decrement(e dom.Event) {
	c.count--
	c.Update()
}

// Lifecycle (optional)
func (c *Counter) OnMount() {
	fmt.Println("Counter mounted with ID:", c.GetID())
}

func main() {
	counter := &Counter{Element: dom.Div(), count: 0}
	dom.Render("app", counter)
	select {}
}
Example 2: Static Component (String HTML)

For simple, static content use string HTML (smaller binary):

type Header struct {
	*dom.Element
}

func (h *Header) RenderHTML() string {
	return `<header class="app-header">
		<h1>My Application</h1>
	</header>`
}

func main() {
	header := &Header{Element: &dom.Element{}}
	dom.Render("app", header)
}

🎨 Fluent Builder API

The new fluent API allows chaining for concise, readable code:

dom.Div().
	ID("container").
	Class("flex items-center").
	Add(
		dom.Button().
			Text("Click me").
			OnClick(handleClick),
		dom.Span().
			Text("Hello World"),
	).
	Render("app") // Terminal operation

Available builders: Div(), Span(), Button(), H1(), H2(), H3(), P(), Ul(), Li(), Input(), Form(), A(), Img()

πŸ”„ Lifecycle Hooks

Components can implement optional lifecycle interfaces:

type MyComponent struct {
	*dom.Element
	data []string
}

// Called after component is mounted to DOM
func (c *MyComponent) OnMount() {
	c.data = fetchData()
	c.Update()
}

// Called after re-render (dom.Update)
func (c *MyComponent) OnUpdate() {
	fmt.Println("Component updated")
}

// Called before component is removed
func (c *MyComponent) OnUnmount() {
	// Cleanup resources
}

πŸ“ Component Interface

All components must implement:

type Component interface {
	GetID() string
	SetID(string)
	RenderHTML() string  // OR Render() dom.Node
	Children() []Component
}

Two rendering options:

  1. RenderHTML() string - For static components (smaller binary)
  2. Render() dom.Node - For dynamic components (type-safe, composable)

Components can implement either or both. DOM checks Render() first, falls back to RenderHTML().

🎯 Hybrid Rendering Strategy

Choose the right rendering method for each component:

Component Type Method Benefit
Static (no interactivity) RenderHTML() string Smaller binary, less overhead
Dynamic (interactive, state) Render() dom.Node Type-safe, composable, fluent API
// Static: Use string HTML
type Footer struct {
	*dom.Element
}
func (f *Footer) RenderHTML() string {
	return `<footer>Β© 2026 My App</footer>`
}

// Dynamic: Use DSL Builder
type TodoList struct {
	*dom.Element
	todos []string
}
func (t *TodoList) Render() dom.Node {
	list := dom.Ul() // ID is auto-injected from t.GetID()
	for _, todo := range t.todos {
		list.Add(dom.Li().Text(todo))
	}
	return list.ToNode()
}

🧩 Nested Components

Components can contain child components:

type MyList struct {
	*dom.Element
	items []dom.Component
}

func (c *MyList) Children() []dom.Component {
	return c.items
}

func (c *MyList) Render() dom.Node {
	list := dom.Div()
	for _, item := range c.items {
		list.Add(item) // Components can be children
	}
	return list.ToNode()
}

When you call dom.Render("app", myList), the library will:

  1. Render the HTML
  2. Call OnMount() for MyList
  3. Recursively call OnMount() for all items

The same recursion applies to Unmount(), ensuring all event listeners are cleaned up.

🎯 Event Handling

func (c *MyComponent) OnMount() {
	root, _ := dom.Get(c.GetID())

	// Reference interface focus on reading and interaction
	root.On("click", func(e dom.Event) {
		id := e.TargetID() // "item-1", "item-2", etc.
		// Handle logic...
	})
}

πŸ”§ Core API

Package Functions
// Rendering
dom.Render(parentID, component)  // Replace parent's content
dom.Append(parentID, component)  // Append after last child
dom.Hydrate(parentID, component) // Attach to server-rendered HTML
dom.Update(component)            // Re-render in place

// Lifecycle
dom.Unmount(component)           // Remove and cleanup

// Element Access (returns Reference interface)
dom.Get(id)                      // Get element by ID
dom.QueryAll(selector)           // Query by CSS selector

// Routing (hash-based)
dom.OnHashChange(handler)        // Listen to hash changes
dom.GetHash()                    // Get current hash
dom.SetHash(hash)                // Set hash
Element Helpers

Embedding *dom.Element provides these methods automatically:

type Counter struct {
	*dom.Element
	count int
}

// Chainable helpers
counter.Update()              // Trigger re-render
counter.Unmount()             // Remove from DOM
counter.GetID()               // Get unique ID
counter.SetID("my-id")        // Set custom ID

πŸ“š Documentation

For more detailed information, please refer to the documentation in the docs/ directory:

  1. Specification & Philosophy: Design goals, architecture, and key decisions.
  2. API Reference: Detailed definition of DOM, Element, and Component interfaces.
  3. Creating Components: Guide to building basic and nested components.
  4. Event Handling: Using the Event interface for clicks, inputs, and forms.
  5. Advanced Patterns: Dynamic lists, decoupling, and performance tips.
  6. Comparison: TinyDOM vs. syscall/js, VDOM, and JS frameworks.

πŸ†• What's New in v0.2.0

  • βœ… Elm-inspired architecture - Component-local state with explicit updates
  • βœ… Fluent Builder API - Chainable methods (dom.Div().ID("x").Class("y"))
  • βœ… Hybrid rendering - Choose DSL or string HTML per component
  • βœ… Lifecycle hooks - OnMount, OnUpdate, OnUnmount
  • βœ… Auto-ID generation - All components get unique IDs automatically
  • βœ… Smaller binaries - TinyGo-optimized, <500KB for typical apps

πŸ“Š Performance

Binary Size (TinyGo WASM):

  • Simple counter app: ~35KB (compressed)
  • Todo list with 10 components: ~120KB (compressed)
  • Full application: <500KB (compressed)

Compared to standard library approach: 60-80% smaller binaries.

License

MIT

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

func Append ΒΆ added in v0.2.0

func Append(parentID string, component Component) error

Append injects a component AFTER the last child of the parent element.

func GetHash ΒΆ added in v0.0.11

func GetHash() string

GetHash gets the current hash.

func Hydrate ΒΆ added in v0.2.0

func Hydrate(parentID string, component Component) error

Hydrate attaches event listeners to existing HTML.

func Log ΒΆ added in v0.0.7

func Log(v ...any)

Log provides logging functionality.

func OnHashChange ΒΆ added in v0.0.11

func OnHashChange(handler func(hash string))

OnHashChange registers a hash change listener.

func Render ΒΆ added in v0.2.0

func Render(parentID string, component Component) error

Render injects a component into a parent element.

func SetHash ΒΆ added in v0.0.11

func SetHash(hash string)

SetHash sets the current hash.

func SetLog ΒΆ added in v0.0.7

func SetLog(log func(v ...any))

SetLog sets the logging function.

func Unmount ΒΆ added in v0.0.7

func Unmount(component Component)

Unmount removes a component from the DOM.

func Update ΒΆ added in v0.2.0

func Update(component Component) error

Update re-renders a component.

Types ΒΆ

type CSSProvider ΒΆ added in v0.0.13

type CSSProvider interface {
	RenderCSS() string
}

CSSProvider is an optional interface for components that need to inject CSS.

type Component ΒΆ

type Component interface {
	GetID() string
	SetID(id string)
	RenderHTML() string
	Children() []Component
}

Component is the minimal interface for components. All components must implement this for both SSR (backend) and WASM (frontend).

type DOM ΒΆ

type DOM interface {
	// Get retrieves an element by its ID.
	// It uses an internal cache to avoid repeated DOM lookups.
	// Returns the element and a boolean indicating if it was found.
	Get(id string) (Reference, bool)

	// Render injects a component into a parent element.
	// 1. It calls component.Render() (if ViewRenderer) or component.RenderHTML()
	// 2. It sets the content of the parent element (found by parentID)
	// 3. It calls component.OnMount() to bind events
	Render(parentID string, component Component) error

	// Append injects a component AFTER the last child of the parent element.
	// Useful for dynamic lists.
	Append(parentID string, component Component) error

	// Hydrate attaches event listeners to existing HTML without re-rendering it.
	Hydrate(parentID string, component Component) error

	// OnHashChange registers a listener for URL hash changes.
	OnHashChange(handler func(hash string))

	// GetHash returns the current URL hash (e.g., "#help").
	GetHash() string

	// SetHash updates the URL hash.
	SetHash(hash string)

	// QueryAll finds all elements matching a CSS selector.
	QueryAll(selector string) []Reference

	// Unmount removes a component from the DOM (by clearing the parent's HTML or removing the node)
	// and cleans up any event listeners registered via the Element interface.
	Unmount(component Component)

	// Update re-renders the component in its current position in the DOM.
	Update(component Component) error

	// Log provides logging functionality using the log function passed to New.
	Log(v ...any)
}

DOM is the main entry point for interacting with the browser. It is designed to be injected into your components.

type Element ΒΆ

type Element struct {
	// contains filtered or unexported fields
}

Element represents a DOM element in the fluent Element API.

func A ΒΆ added in v0.2.0

func A() *Element

func Button ΒΆ added in v0.2.0

func Button() *Element

func Div ΒΆ added in v0.2.0

func Div() *Element

Factory functions

func Form ΒΆ added in v0.2.0

func Form() *Element

func H1 ΒΆ added in v0.2.0

func H1() *Element

func H2 ΒΆ added in v0.2.0

func H2() *Element

func H3 ΒΆ added in v0.2.0

func H3() *Element

func Img ΒΆ added in v0.2.0

func Img() *Element

func Input ΒΆ added in v0.2.0

func Input() *Element

func Li ΒΆ added in v0.2.0

func Li() *Element

func P ΒΆ added in v0.2.0

func P() *Element

func Span ΒΆ added in v0.2.0

func Span() *Element

func Ul ΒΆ added in v0.2.0

func Ul() *Element

func (*Element) Add ΒΆ added in v0.2.3

func (b *Element) Add(children ...any) *Element

Add adds one or more children to the element. Children can be *Element, Node, Component, or string.

func (*Element) Append ΒΆ added in v0.2.3

func (b *Element) Append(child any) *Element

Append adds a child to the element. Deprecated: use Add instead.

func (*Element) Attr ΒΆ added in v0.2.3

func (b *Element) Attr(key, val string) *Element

Attr sets an attribute on the element.

func (*Element) Children ΒΆ added in v0.2.3

func (b *Element) Children() []Component

Children returns the component's children (components only).

func (*Element) Class ΒΆ added in v0.2.3

func (b *Element) Class(class ...string) *Element

Class adds a class to the element.

func (*Element) GetID ΒΆ added in v0.2.3

func (b *Element) GetID() string

GetID returns the element's ID.

func (*Element) ID ΒΆ added in v0.2.3

func (b *Element) ID(id string) *Element

ID sets the ID of the element.

func (*Element) OnChange ΒΆ added in v0.2.3

func (b *Element) OnChange(handler func(Event)) *Element

OnChange adds a change event handler.

func (*Element) OnClick ΒΆ added in v0.2.3

func (b *Element) OnClick(handler func(Event)) *Element

OnClick adds a click event handler.

func (*Element) OnInput ΒΆ added in v0.2.3

func (b *Element) OnInput(handler func(Event)) *Element

OnInput adds an input event handler.

func (*Element) Render ΒΆ added in v0.2.3

func (b *Element) Render(parentID string) error

Render renders the element to the parent. This is a terminal operation.

func (*Element) RenderHTML ΒΆ added in v0.2.3

func (b *Element) RenderHTML() string

RenderHTML renders the element to HTML string.

func (*Element) SetID ΒΆ added in v0.2.3

func (b *Element) SetID(id string)

SetID sets the element's ID.

func (*Element) Text ΒΆ added in v0.2.3

func (b *Element) Text(text string) *Element

Text adds a text node child.

func (*Element) ToNode ΒΆ added in v0.2.3

func (b *Element) ToNode() Node

ToNode converts the element to a Node tree.

func (*Element) Unmount ΒΆ added in v0.3.0

func (b *Element) Unmount()

Unmount removes the component from the DOM.

func (*Element) Update ΒΆ added in v0.3.0

func (b *Element) Update() error

Update triggers a re-render of the component.

type Event ΒΆ

type Event interface {
	// PreventDefault prevents the default action of the event.
	PreventDefault()
	// StopPropagation stops the event from bubbling up the DOM tree.
	StopPropagation()
	// TargetValue returns the value of the event's target element.
	// Useful for input, textarea, and select elements.
	TargetValue() string
	// TargetID returns the ID of the event's target element.
	TargetID() string
}

Event represents a DOM event.

type EventHandler ΒΆ added in v0.2.0

type EventHandler struct {
	Name    string
	Handler func(Event)
}

EventHandler represents a DOM event handler in the declarative builder.

type IconSvgProvider ΒΆ added in v0.0.13

type IconSvgProvider interface {
	IconSvg() map[string]string
}

IconSvgProvider is an optional interface for components that provide SVG icons.

type JSProvider ΒΆ added in v0.0.13

type JSProvider interface {
	RenderJS() string
}

JSProvider is an optional interface for components that need to inject JS.

type Mountable ΒΆ added in v0.2.0

type Mountable interface {
	OnMount()
}

Mountable is an optional interface for components that need initialization logic.

type Node ΒΆ added in v0.2.0

type Node struct {
	Tag      string
	Attrs    []fmt.KeyValue
	Events   []EventHandler
	Children []any // Can be Node, string, or Component
}

Node represents a DOM node in the declarative builder.

type Reference ΒΆ added in v0.2.3

type Reference interface {

	// GetAttr retrieves an attribute value.
	GetAttr(key string) string

	// Value returns the current value of an input/textarea/select.
	Value() string

	// Checked returns the current checked state of a checkbox or radio button.
	Checked() bool

	// On registers a generic event handler (e.g., "click", "change", "input", "keydown").
	On(eventType string, handler func(event Event))

	// Focus sets focus to the element.
	Focus()
}

Reference represents a reference to a DOM node. It provides methods for reading and interaction.

func Get ΒΆ added in v0.0.7

func Get(id string) (Reference, bool)

Get retrieves an element by its ID.

func QueryAll ΒΆ added in v0.0.11

func QueryAll(selector string) []Reference

QueryAll query elements.

type Unmountable ΒΆ added in v0.2.0

type Unmountable interface {
	OnUnmount()
}

Unmountable is an optional interface for components that need cleanup logic.

type Updatable ΒΆ added in v0.2.0

type Updatable interface {
	OnUpdate()
}

Updatable is an optional interface for components that need update logic.

type ViewRenderer ΒΆ added in v0.2.0

type ViewRenderer interface {
	Render() Node
}

ViewRenderer returns a Node tree for declarative UI.

Directories ΒΆ

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL