package app import ( . "kjol/wasmruntime/vdom" ui "kjol/webui" ) var chartPageDays = []string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"} // chartDemoData is a week of values; a non-zero seed reshuffles them deterministically, so // "Shuffle" changes the chart without a data source and without a random that would differ // between the server's render and the client's first one. func chartDemoData(seed int) []float64 { base := []float64{42, 17, 63, 28, 55, 9, 71} if seed == 0 { return base } out := make([]float64, len(base)) for i, b := range base { m := (int(b)*7 + seed*13) % 80 if m < 4 { m = 4 } out[i] = float64(m) } return out } //gowasm:page /wasm/chart static layout=app func ChartPage(d Deps) func() *VNode { seed := NewSignal(0) barChart := ui.NewChart() areaChart := ui.NewChart() return func() *VNode { values := chartDemoData(seed.Get()) return docPage("Rendering", "SSR & hydration", "A static route is rendered to HTML by the server, so the page is complete before any "+ "WebAssembly has downloaded. The same component then runs in the browser, adopts the markup "+ "that is already there, and takes over. One function, two runtimes.", docSection("the-directive", "Marking a route static", prose("static on the page directive is what puts a route in the server's pre-render set. Leave "+ "it off and the route renders on the client only — which is the right choice when the page "+ "is behind a login, or its content depends on something only the browser knows."), code("app/chart.go", chartSnippet), note("Hydration adopts, it does not rebuild", "The client renders the same tree the server did and walks the existing DOM alongside it, "+ "wiring event handlers to the nodes that are already on the page. If the two trees "+ "disagree, the CLIENT wins — a stale server binary should not be able to pin a wrong "+ "class onto the page forever."), ), docSection("charts", "A worked example: a chart", prose("The two charts below are webui.Chart. Their SVG — axes, rounded columns, the smoothed "+ "area — is drawn on the SERVER and shipped in the page's HTML: view source and the marks "+ "are already there, complete before any WebAssembly runs. That is what static buys."), prose("What the server cannot send is the interaction. The hover tooltip and the resize-to-fit "+ "come alive when the WebAssembly hydrates the page — it adopts the SVG already on screen and "+ "wires the pointer handlers to it, redrawing nothing. Server-rendered picture, client-side "+ "behaviour, one component. Shuffle re-renders it in the browser, and no request is made."), Div(Attr("class", "mt-4"), ui.Button(ui.ButtonProps{Color: ui.ButtonPrimary, Icon: "chart-column", Text: "Shuffle data", OnClick: func() { seed.Set(seed.Get() + 1) }}), ), Div(Attr("class", "mt-4 grid gap-4 lg:grid-cols-2"), Div(Attr("class", "rounded-default border border-line bg-surface p-3 shadow-xs"), barChart.Render(ui.ChartProps{Kind: ui.ChartBar, Labels: chartPageDays, Series: []ui.ChartSeries{{Name: "Requests", Data: values}}, Height: 240})), Div(Attr("class", "rounded-default border border-line bg-surface p-3 shadow-xs"), areaChart.Render(ui.ChartProps{Kind: ui.ChartArea, Smooth: true, Labels: chartPageDays, Series: []ui.ChartSeries{{Name: "Requests", Data: values}}, Height: 240})), ), ), docSection("api", "Reference", apiTable( apiRow{"//gowasm:page /path static", "Pre-render this route on the server, then hydrate it."}, apiRow{"vdom.RenderHTML", "Render a tree to an HTML string. This is what the server calls."}, apiRow{"wasmruntime.Hydrate", "Adopt server-rendered DOM instead of building it. The client's entry point for a static route."}, apiRow{"vdom.Raw", "Insert markup verbatim — how webui.Chart's SVG string enters the tree. The reconciler clears it correctly when the element is reused."}, ), ), ) } } const chartSnippet = `//gowasm:page /wasm/chart static layout=app func ChartPage(d Deps) func() *VNode { seed := NewSignal(0) chart := ui.NewChart() // a controller: holds hover + refs across renders return func() *VNode { return docPage("Rendering", "SSR & hydration", // The chart's SVG is rendered on the SERVER and shipped in the HTML. // On hydration the client adopts those nodes and wires the pointer // handlers — the hover tooltip comes alive without redrawing anything. chart.Render(ui.ChartProps{ Kind: ui.ChartBar, Labels: []string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}, Series: []ui.ChartSeries{{Name: "Requests", Data: chartDemoData(seed.Get())}}, }), ui.Button(ui.ButtonProps{Text: "Shuffle", OnClick: func() { seed.Set(seed.Get() + 1) // a signal write re-renders, in the browser }}), ) } }`