add WASM blazor-like thing

This commit is contained in:
2026-07-13 00:40:08 -04:00
parent 98978e4930
commit 3c494605ba
38 changed files with 3070 additions and 1 deletions

86
go/wasmruntime/mount.go Normal file
View File

@@ -0,0 +1,86 @@
//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
}
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
}
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
}