//go:build !(js && wasm) package rsc import ( "encoding/gob" "net/http" "sort" "sync" "kjol/wasmruntime/vdom" ) // registry maps a server-component name to its factory (created by generated // code — one Register per //gowasm:server component). The factory builds the // component's signals + render closure; here it's re-run per request (stateless). var registry = map[string]func() func() *vdom.VNode{} func Register(name string, factory func() func() *vdom.VNode) { registry[name] = factory } // renderMu serializes server renders because the vdom signal Collector is a // process-global (see vdom.BeginCollect). Fine for this scale. var renderMu sync.Mutex // Handler is the single /rsc endpoint. It restores the component's signals from // the request, applies the event (if any), re-renders, and returns the new // signal snapshot + tree. func Handler(w http.ResponseWriter, r *http.Request) { var req Request if gob.NewDecoder(r.Body).Decode(&req) != nil { http.Error(w, "bad request", http.StatusBadRequest) return } factory := registry[req.Name] if factory == nil { http.NotFound(w, r) return } renderMu.Lock() c := vdom.BeginCollect(req.State) // restore prior signal values (nil on mount) render := factory() // signals created + restored here vdom.EndCollect() if req.Kind == "event" { // Reproduce the tree the client currently shows (same state) to find the // handler by node id, then invoke it (mutating signals). _, handlers := renderIDs(render()) if hm := handlers[req.NodeID]; hm != nil { if h := hm[req.Event]; h != nil { h(serverEvent{value: req.Value}) } } } tree, _ := renderIDs(render()) // render the result (reflects the mutation) resp := Response{State: c.Snapshot(), Tree: tree} renderMu.Unlock() w.Header().Set("Content-Type", "application/octet-stream") _ = gob.NewEncoder(w).Encode(resp) } // renderIDs serializes a VNode tree to SNode, assigning a stable (traversal // order) id to each node with handlers and building the handler table. func renderIDs(root *vdom.VNode) (*SNode, map[int]map[string]func(vdom.Event)) { handlers := map[int]map[string]func(vdom.Event){} id := 0 var walk func(*vdom.VNode) *SNode walk = func(v *vdom.VNode) *SNode { sn := &SNode{Tag: v.Tag, Text: v.Text, HTML: v.HTML, Attrs: v.Attrs} if len(v.Events) > 0 { id++ sn.ID = id hm := map[string]func(vdom.Event){} for ev, h := range v.Events { sn.Events = append(sn.Events, ev) hm[ev] = h } sort.Strings(sn.Events) handlers[id] = hm } if v.HTML == "" { for _, k := range v.Children { sn.Kids = append(sn.Kids, walk(k)) } } return sn } return walk(root), handlers } // serverEvent is the vdom.Event a server component sees when the client replays a // handler invocation. Only the target's value survives the round trip (it is all // the client sends); everything else — key, pointer coordinates, the DOM target, // the DataTransfer — is meaningless on the server and reads as its zero value. type serverEvent struct{ value string } func (e serverEvent) PreventDefault() {} func (e serverEvent) StopPropagation() {} func (e serverEvent) Value() string { return e.value } func (e serverEvent) Checked() bool { return false } func (e serverEvent) Key() string { return "" } func (e serverEvent) ClientX() int { return 0 } func (e serverEvent) ClientY() int { return 0 } func (e serverEvent) Target() any { return nil } func (e serverEvent) SetData(_, _ string) {} func (e serverEvent) GetData(string) string { return "" }