//go:build js && wasm package wasmruntime import ( "syscall/js" "kjol/vdom" ) var ( rootRender func() renderScheduled bool disposed bool flushFunc js.Func flushInited bool ) // HasServerContent reports whether #app already holds server-rendered markup // (so the client should Hydrate rather than build from scratch). func HasServerContent() bool { return document.Call("getElementById", "app").Get("firstChild").Truthy() } // Run mounts a component fresh (client-side rendering). The component returns a // VNode tree; any signal write re-renders and reconciles into the DOM. func Run(component func() *vdom.VNode) { vdom.Schedule = scheduleRender root := document.Call("getElementById", "app") var prev *vdom.VNode rootRender = func() { next := component() patchChildren(root, one(prev), one(next)) prev = next flushAfterRender() // refs are live now — measurement callbacks can run } rootRender() finish(root) } // Hydrate attaches to server-rendered DOM: it renders the same initial tree the // server did, adopts the existing nodes (wiring events, no re-creation), then // re-renders reactively from there. func Hydrate(component func() *vdom.VNode) { vdom.Schedule = scheduleRender root := document.Call("getElementById", "app") prev := component() hydrateNode(root.Get("firstChild"), prev) rootRender = func() { next := component() patchChildren(root, one(prev), one(next)) prev = next flushAfterRender() } flushAfterRender() // the adopted DOM is live; refs from hydration are usable finish(root) } func finish(root js.Value) { // Allow a JS loader to tear this instance down before an in-place hot swap: // snapshot signal state (so the next instance restores it), then clear #app. js.Global().Set("__gowasmDispose", js.FuncOf(func(this js.Value, args []js.Value) any { disposed = true saveState() root.Set("innerHTML", "") return nil })) select {} // keep the runtime alive for event callbacks } func scheduleRender() { if disposed || rootRender == nil || renderScheduled { return } renderScheduled = true js.Global().Call("queueMicrotask", ensureFlush()) } func ensureFlush() js.Func { if !flushInited { flushFunc = js.FuncOf(func(this js.Value, args []js.Value) any { renderScheduled = false rootRender() return nil }) flushInited = true } return flushFunc }