111 lines
5.0 KiB
Go
111 lines
5.0 KiB
Go
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
|
|
}}),
|
|
)
|
|
}
|
|
}`
|