Documentation
¶
Overview ¶
Package domi is a server-rendered framework for building browser applications in Go. An application is a state machine: it implements App, whose View method renders the current state as a Node tree and whose Update method transitions the state in response to events. The framework hosts the application behind an http.Handler, keeps the browser's view in sync with whatever View returns, and dispatches user-generated events back through Update.
The package exposes only the primitives needed to build any node or attribute (Tag, Keyed, Fragment, Text, Name, Group, On). Convenience wrappers for common HTML tags, attributes, and events live in ily.dev/domi/html, ily.dev/domi/attr, and ily.dev/domi/event.
A Node is anything that can appear in the tree: text, an element built via Tag, or a keyed element built via Keyed. Tag and Keyed return curried builders — first attributes, then children. An element with no children is itself a Node, so void elements (e.g. Br, Input) and other childless tags can appear in a parent's child list without a trailing empty children call.
Attribute names beginning with "data-domi-" are reserved for use by this package and its subpackages. Application code and third-party packages should pick data attributes outside that prefix to avoid collisions with framework internals — present or future.
Bundling the Client ¶
The client-side runtime for this module lives in file client.js at the module root. Apps using the default Handler without further customization don't need to access this file directly. The framework includes it in the default document head.
Apps that provide their own document shell can add client.js into their JavaScript bundle alongside their app code. The filesystem path of client.js is:
$(go list -m -f '{{.Dir}}' ily.dev/domi)/client.js
Include this path in your JavaScript bundle, import the module, and call run:
import * as Domi from "path/to/client.js"; Domi.run(); // after document.body is ready
Index ¶
- Variables
- func Bool(name string) func(bool) Attr
- func Handler[Msg any, A App[Msg]](f func(context.Context, *url.URL) (A, Cmd[Msg]), ...) http.Handler
- func Keyed(name string) func(...Attr) func(iter.Seq2[string, Node]) Node
- func Name(name string) func(value string) Attr
- func On(event string) func(msg any) Attr
- func RegisterCombining(name, sep string)
- func SessionID(ctx context.Context) string
- func Tag(name string) func(...Attr) Element
- type App
- type Attr
- type Cmd
- type Element
- type Node
- type Option
- type Sub
- type URLRequest
Constants ¶
This section is empty.
Variables ¶
var ( // Bypass annotates a link to use the browser's built-in navigation, // rather than going through the framework. Bypass = Bool("data-domi-bypass") // Opaque marks an element as opaque, ignored by the virtual DOM diff. // Such an element is inserted, // and then never modified until its eventual removal (if any). // Any changes to its contents during its existence are ignored. // This allows client-side browser code to take ownership of the element // without worrying about patches modifying it underfoot. // // An opaque element must be a keyed child. See [Keyed]. // Inserting an opaque node anywhere else panics. Opaque = Bool("data-domi-opaque")(true) )
Functions ¶
func Bool ¶
Bool returns a builder for a boolean HTML attribute. For standard boolean attributes (disabled, checked, …), true means present (name-only) and false means absent:
Tag("input")(attr.Disabled(true))() // <input disabled>
Tag("input")(attr.Disabled(false))() // <input>
For enumerated boolean attributes (contenteditable, draggable, spellcheck, translate), true and false emit the corresponding string value instead:
Tag("div")(attr.ContentEditable(true))() // <div contenteditable="true">
Tag("div")(attr.ContentEditable(false))() // <div contenteditable="false">
func Handler ¶
func Handler[Msg any, A App[Msg]]( f func(context.Context, *url.URL) (A, Cmd[Msg]), onURLRequest func(URLRequest) Msg, onURLChange func(*url.URL) Msg, o ...Option, ) http.Handler
Handler serves an App.
At the start of a session, the returned Handler calls f, providing the initial request URL, to obtain a fresh App instance plus an initial Cmd. The context carries the session ID (see SessionID) and is cancelled when the session ends. This instance is associated with the session, so each browser gets its own independent state.
When the user clicks a link, the framework intercepts the navigation and calls onURLRequest to produce a Msg. The app's Update decides how to handle the request, typically by returning a PushURL or ReplaceURL command.
When the URL changes (from a navigation command or browser back/forward), the framework calls onURLChange to produce a Msg. The app's Update typically translates the URL into a route and updates its state accordingly.
func Keyed ¶
Keyed returns a curried builder for an element whose children are paired with stable keys. The framework reconciles updates to keyed children by identity rather than position, so inserting, removing, or reordering items in the middle of a list updates the surviving children in place instead of replacing the entire affected suffix.
The children sequence yields (key, child) pairs in the desired order:
Keyed("ul")(attr.Class("items"))(func(yield func(string, Node) bool) {
for _, it := range items {
if !yield(itemKey(it), itemRow(it)) {
return
}
}
})
Each yielded child must be an element; text and Fragment children cannot be keyed, and Keyed panics on a non-element child. Keys must be unique within the sequence and stable across renders for the same logical item.
A child of Keyed can optionally use the Opaque attr. See Opaque for details on its behavior.
func Name ¶
Name returns a builder for an HTML attribute with the given name. (e.g. class). Call it to obtain an Attr with the given value (e.g. class="foo").
func On ¶
On returns a builder for an attribute that binds msg to event on the resulting element. When the browser fires the named event, the framework calls Update(msg).
Multiple On(event)(...) attributes on the same element all fire when the event occurs.
If msg has a field tagged domi:"event", the framework unmarshals the event into that field. See ily.dev/domi/event.Event for a convenience type that captures everything the client sends.
If msg cannot be marshaled to JSON, On panics.
func RegisterCombining ¶
func RegisterCombining(name, sep string)
RegisterCombining registers name as a "combining" attribute. When a combining attribute appears more than once in an HTML node, the values are combined, separated by sep, into a single attribute in the rendered output.
RegisterCombining must be called before Handler. This is typically done in an init function in packages that define custom attributes.
The initial set of combining attributes is:
name sep class " " style ";"
func Tag ¶
Tag returns a curried builder for an HTML element with the given name. Its first call takes attributes, and the second takes children.
Tag("div")(attr.Class("x"))(Text("hi"))
Void elements (and any other "no children" case) can skip the trailing empty children call. Element is itself a Node.
Div()(Text("a"), Br(), Text("b"))
Helpers for common tags can be found in ily.dev/domi/html.
Child nodes must not use the Opaque attr. If a child node is opaque, Tag panics. See Keyed to use opaque nodes.
Types ¶
type App ¶
type App[Msg any] interface { // Update is responsible for updating the App state // in response to each Msg. It must not produce external // side-effects, only update its internal state. // // The context carries the session ID (see SessionID) // as well as values from the HTTP request context, if any. // It is cancelled when the session ends. // // For external side-effects, such as database writes, // Update should return a [Cmd]. Update(context.Context, Msg) Cmd[Msg] // View returns the document title and body tree // to be displayed in the browser. // // The context carries the session ID (see SessionID) // as well as values from the HTTP request context, if any. // It is cancelled when the session ends. View(context.Context) (title string, n Node) // Subscriptions returns the set of active subscriptions. // The framework diffs this set between update cycles. // New subscriptions are connected to the App, // absent subscriptions are canceled. // // The context carries the session ID (see SessionID) // as well as values from the HTTP request context, if any. // It is cancelled when the session ends. Subscriptions(context.Context) Sub[Msg] // Preview returns the document title and body tree // that would be displayed if the browser navigated // to the given URL, and whether the navigation is allowed. // It must not modify the App's state. // // The framework calls Preview to pre-render pages // the user is likely to visit (e.g. on link hover), // so navigation appears instant when the link is clicked. // When ok is false the framework discards the title and view // and the user's click falls back to normal navigation, // giving the app a chance to deny or redirect. // // The context carries the session ID (see SessionID) // as well as values from the HTTP request context, if any. // It is cancelled when the session ends. Preview(context.Context, *url.URL) (title string, n Node, ok bool) }
App is the state machine provided by a domi application. One instance holds the state for a single browser sesssion. See Handler for session lifecycle.
type Attr ¶
type Attr interface {
// contains filtered or unexported methods
}
An Attr is an HTML attribute.
In rendered output, a single attribute name does not appear more than once on any given element:
- For each combining attribute, the framework combines the values into a single value. See RegisterCombining for more.
- Event handlers are combined internally.
- For all other attributes, only the first occurrence appears.
type Cmd ¶
type Cmd[Msg any] struct { // contains filtered or unexported fields }
A Cmd is a deferred side-effect that eventually produces a Msg. The framework runs each Cmd in its own goroutine and passes the resulting Msg back into Update.
func Batch ¶
Batch returns a Cmd that runs each item in c concurrently. The resulting Msg values are dispatched to Update serially.
func Func ¶
Func returns a Cmd that runs fn and dispatches its result back into Update.
The app should capture the context from [Update] or the Handler constructor for f to use.
func Load ¶
Load returns a Cmd that triggers a full-page browser navigation to url, leaving the current session behind. Unlike PushURL and ReplaceURL, which update the history of the running application, Load performs a real navigation (window.location): the browser discards the current document and fetches a fresh one. The url may therefore be absolute and cross-origin.
Load is the escape hatch for links the application does not route itself — logging out, leaving for an external site, or following a same-origin link served outside the domi app. The app returns it from Update in response to a URLRequest it decides not to handle internally. To opt a link out of interception ahead of time, without a server round trip, give the anchor the data-domi-bypass attribute instead.
func PushURL ¶
PushURL returns a Cmd that changes the browser URL and adds an entry to the navigation history. The url must be an origin-relative URL (path, query, fragment) with no scheme or host.
The resulting Cmd dispatches the onURLChange callback registered on Handler so the app can update its route state, and bundles the history.pushState instruction into the same SSE frame as the DOM patches from that update.
func ReplaceURL ¶
ReplaceURL returns a Cmd that changes the browser URL without adding an entry to the navigation history. The url must be an origin-relative URL (path, query, fragment) with no scheme or host.
The resulting Cmd dispatches the onURLChange callback registered on Handler so the app can update its route state, and bundles the history.replaceState instruction into the same SSE frame as the DOM patches from that update.
type Element ¶
Element is the partially-applied builder returned by Tag(name)(attrs). Calling it with children produces a finished element node; an Element with no children is itself a Node, so childless tags can appear in a parent's child list without a trailing empty children call.
Child nodes must not use the Opaque attr. If a child node is opaque, Element panics. See Keyed to use opaque nodes.
type Node ¶
type Node interface {
// contains filtered or unexported methods
}
A Node is an HTML node, a Text, Raw, Tag, Keyed, or Fragment.
func Fragment ¶
A Fragment is a sequence of HTML nodes. It contributes its contents to its parent's child list in order, as if they had been written there directly.
Fragments may be nested arbitrarily. A Fragment cannot be keyed. Keyed children must each be a single element with an identity.
func Raw ¶
Raw returns a node whose content is written verbatim, without HTML escaping. Use Raw for trusted HTML: inline SVG, pre-sanitized markdown output, or fragments from third-party HTML generators. Never pass user-controlled input to Raw without prior sanitization.
The content must produce exactly one DOM node when parsed: either a text string containing no markup, or a single properly nested HTML element with explicit closing tags. Raw panics if the content is empty, contains multiple top-level nodes, or has unclosed tags.
func Safe ¶
Safe parses the given HTML string and returns a sanitized Node tree. It uses a hard-coded allowlist of tag names and tag-attribute pairs. Tags not in the allowlist are unwrapped (children preserved). Dangerous tags like script and style are removed entirely, including their children.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
An Option configures a Handler.
func Document ¶
Document supplies a custom builder for the initial HTML shell. The builder is called once per session with the initial document title and the body element. It is responsible for returning a complete html element. The framework always writes the HTML5 doctype declaration before the html element.
domi.Handler(newApp, domi.Document(func(title string, body domi.Node) domi.Node {
return html.HTML()(
html.Head()(
html.Meta(attr.Charset("utf-8")),
html.Title()(domi.Text(title)),
html.Script(attr.Type("module"), attr.Src("/bundle.js")),
),
body,
)
}))
Apps using Document are responsible for loading the Domi client JavaScript. See [Bundling the Client] for details.
func Keepalive ¶
Keepalive sets the maximum SSE connection idle time before the server sends an SSE comment line to the client. Keepalives keep proxies from killing an idle connection. The default interval is 25 seconds.
func Logger ¶
Logger sets the structured logger used by the framework for internal diagnostics such as malformed client events and handler registry misses. The default logger is slog.Default.
func ReplayWindow ¶
ReplayWindow sets how many recent patch frames a session retains for SSE clients to resume from after a transient disconnect. Clients reconnecting within this window receive only the patches they missed; clients further behind get a full resync of the current view. The default window is 128 frames.
func SessionTimeout ¶
SessionTimeout sets how long a session can remain idle before the framework releases it. The default timeout is 48 hours.
type Sub ¶
type Sub[Msg any] struct { // contains filtered or unexported fields }
A Sub is a long-lived event source that produces Msg values in response to external stimuli. The zero value of Sub is a valid Sub that emits no messages.
func Subscription ¶
Subscription creates a Sub that runs f and dispatches each yielded Msg back into Update. The framework uses key to identify this subscription. If a key persists between update cycles, the source stays alive. If it disappears, the source is cancelled.
The Seq returned from f must exit when its context becomes done, in addition to exiting when yield returns false.
type URLRequest ¶
A URLRequest represents a user clicking a link in the browser. The framework intercepts the click, prevents the default navigation, and dispatches the request through the onURLRequest callback registered on Handler.
Internal is true when the link target shares the current page's origin (same scheme, host, and port). For internal requests the app typically returns a PushURL command; for external requests it may ignore the event or navigate with a full page load.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package attr provides prebound constructors for common HTML attributes.
|
Package attr provides prebound constructors for common HTML attributes. |
|
Package event provides prebound constructors for common DOM event handlers (event.Click, event.Submit, event.Input) and convenience types for the event payload the framework splices into tagged Msg fields.
|
Package event provides prebound constructors for common DOM event handlers (event.Click, event.Submit, event.Input) and convenience types for the event payload the framework splices into tagged Msg fields. |
|
examples
|
|
|
counter
command
|
|
|
nav
command
|
|
|
todos
command
|
|
|
Package html provides prebound builders for common HTML elements.
|
Package html provides prebound builders for common HTML elements. |
|
internal
|
|
|
vdom
Package vdom holds the lowered (canonical) form of the domi virtual DOM, plus the renderer and differ that walk it.
|
Package vdom holds the lowered (canonical) form of the domi virtual DOM, plus the renderer and differ that walk it. |