87 lines
2.2 KiB
Go
87 lines
2.2 KiB
Go
//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
|
|
}
|