LiveView for Go
Phoenix LiveView-style reactive server-rendered UI for Go.
A from-scratch, standard-library-only Go take on Phoenix LiveView: server-held state drives the UI, the browser sends events, and the server ships back a minimal diff describing only what changed. The core trick is LiveView's static/dynamic split — a template is compiled once into the literal fragments that never change and the interpolated values that do, so unchanged HTML never travels twice. You implement the View interface (Mount / HandleEvent / Render), keep per-connection state in a Socket with per-key change tracking, and let a Session run the mount → render → diff cycle; a tiny net/http Handler adds an optional HTTP + JSON transport on top. No cgo, no third-party dependencies — the import path and package are both liveview, and diffs marshal to the same compact JSON shape Phoenix LiveView uses.
Install
$ go get github.com/malcolmston/liveviewQuick start
import "github.com/malcolmston/liveview"
var tmpl = liveview.MustParse(
`<div><h1>{{ label }}</h1><span class="value">{{ count }}</div>`)
type Counter struct{}
func (Counter) Mount(_ map[string]any, s *liveview.Socket) error {
s.Assign("count", 0)
s.Assign("label", "Clicks")
return nil
}
func (Counter) HandleEvent(e string, _ map[string]any, s *liveview.Socket) error {
if e == "inc" {
s.Assign("count", s.GetInt("count")+1)
}
return nil
}
func (Counter) Render(a map[string]any) *liveview.Rendered { return tmpl.Render(a) }Features
- The
Viewlifecycle —Mountseeds state,HandleEventreacts to a client event,Renderreturns a static/dynamic tree - Server-held state in a
Socket—Assign/AssignAllwrites,GetInt/GetString/Getreads, with per-keyChangedtracking - Tiny
{{ name }}templates compiled once viaMustParse/Parseinto a fixed static/dynamicTemplate - A
Renderedtree, not a string —Statics+Dynamicswith thelen(Statics) == len(Dynamics)+1invariant, andHTML()to materialise it - Minimal-patch diff engine —
DiffRenderedemits only the changed dynamic slots (recursing into nested components),FullDiffsends the first frame with statics under"s" - Auto HTML-escaping by default, with
Safeto opt trusted markup out of escaping - A per-connection
Session(NewSession) that drives mount → render → diff;Session.Eventreturns just theDiff - An optional
net/httptransport —NewHandlerserves the initial HTML page and accepts JSON events, so the state → render → diff core stays independent of the wire - Zero dependencies — pure Go standard library, nothing to audit but the toolchain