Update 3d chart mode, add US heatmap, move kjol-web -> kjol-website
This commit is contained in:
150
go/cmd/kjol-website/server/main.go
Normal file
150
go/cmd/kjol-website/server/main.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// Command server runs the kjol-website site on kjol's reusable wasmdevserver:
|
||||
// it SSRs the app's static routes, hosts the /rsc server-component endpoint, and
|
||||
// hot-swaps the wasm into the browser on change. It shows the coupling
|
||||
// inversion — the framework (wasmdevserver) imports no app code; the app injects
|
||||
// Build/Render/Document here.
|
||||
//
|
||||
// Run it from THIS directory (the relative paths below are resolved against it):
|
||||
//
|
||||
// go run ./server # from go/cmd/kjol-website
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"kjol/httputil"
|
||||
"kjol/vdom"
|
||||
"kjol/wasmdevserver"
|
||||
"kjol/webui"
|
||||
|
||||
"kjolwebsite/app"
|
||||
"kjolwebsite/build"
|
||||
"kjolwebsite/internal/handlers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
addr := flag.String("addr", ":8085", "listen address")
|
||||
watch := flag.Bool("watch", true, "watch sources, rebuild wasm, hot-reload")
|
||||
buildOnly := flag.Bool("build", false, "run the full build once and exit (no server)")
|
||||
flag.Parse()
|
||||
|
||||
if *buildOnly {
|
||||
build.Cold()
|
||||
return
|
||||
}
|
||||
|
||||
log.Fatal(wasmdevserver.Serve(wasmdevserver.Config{
|
||||
Addr: *addr,
|
||||
Dir: "./wwwroot",
|
||||
Watch: *watch,
|
||||
WatchDirs: []string{
|
||||
"app", "wasm", "css", // the Go/WASM app
|
||||
"frontend", // the Solid app — a .tsx save rebuilds the JS bundle
|
||||
"../../webui", "../../vdom", "../../wasmruntime", "../../rsc", // the wasm engine
|
||||
"../../lexer", // the code-block highlighter
|
||||
"../../jsruntime/uikit", "../../jsruntime/styles", // the Solid kit + the shared theme
|
||||
},
|
||||
Build: build.All,
|
||||
BuildCSS: build.Tailwind, // a .css save skips codegen+wasm and hot-swaps the stylesheet
|
||||
Render: render,
|
||||
Document: document,
|
||||
Handle: routes,
|
||||
}))
|
||||
}
|
||||
|
||||
// routes registers everything the WASM app does not own.
|
||||
//
|
||||
// Order does not matter here — Go's ServeMux picks the most specific pattern, not the
|
||||
// first — but the shape does: /js/* belongs to a completely different front-end, and it
|
||||
// is claimed BEFORE the wasm app's "/" catch-all ever sees it. Two SPAs, one server, no
|
||||
// argument about who owns a URL.
|
||||
func routes(mux *http.ServeMux) {
|
||||
handlers.RegisterPublicPages(mux) // the SSR'd public pages (/js/ssr)
|
||||
mux.HandleFunc("GET /js/", serveJSApp)
|
||||
mux.HandleFunc("GET /js", serveJSApp)
|
||||
|
||||
// /api/quotes responds with a gob-encoded []app.Quote (via httputil.RespondGob) —
|
||||
// the /wasm/data page fetches and decodes it on the client with encoding/gob (Go
|
||||
// types end to end, no JSON).
|
||||
mux.HandleFunc("GET /api/quotes", func(w http.ResponseWriter, r *http.Request) {
|
||||
httputil.RespondGob(w, http.StatusOK, sampleQuotes())
|
||||
})
|
||||
}
|
||||
|
||||
// serveJSApp ships the shell for the Solid SPA. Every /js/* route gets the SAME empty
|
||||
// document — the client router reads the URL and decides what to render, which is what
|
||||
// makes it a single-page app.
|
||||
//
|
||||
// It carries no server-rendered markup, and that is a real difference from Kjøl Wasm Web
|
||||
// rather than an oversight: this is a docs section behind a click, where a blank first
|
||||
// frame costs nothing. Where it WOULD cost something, the public-page path (see
|
||||
// internal/handlers) renders on the server instead — /js/ssr is that, and it is
|
||||
// registered above, so it never reaches this handler.
|
||||
func serveJSApp(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
fmt.Fprint(w, `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Kjøl JS Web</title>
|
||||
`+webui.ThemeBootScript+`
|
||||
<link rel="stylesheet" href="/bundle.min.css" />
|
||||
</head>
|
||||
<body class="antialiased">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/bundle.min.js"></script>
|
||||
</body>
|
||||
</html>`)
|
||||
}
|
||||
|
||||
func sampleQuotes() []app.Quote {
|
||||
return []app.Quote{
|
||||
{Author: "Rob Pike", Text: "A little copying is better than a little dependency."},
|
||||
{Author: "Rob Pike", Text: "Don't communicate by sharing memory; share memory by communicating."},
|
||||
{Author: "Ken Thompson", Text: "When in doubt, use brute force."},
|
||||
{Author: "Alan Kay", Text: "The best way to predict the future is to invent it."},
|
||||
}
|
||||
}
|
||||
|
||||
// render SSRs a static route's #app inner HTML; ok=false ships an empty #app
|
||||
// (client-rendered). It is the same neutral render the client runs, so the client
|
||||
// hydrates it.
|
||||
func render(path string) (string, bool) {
|
||||
if !app.StaticPaths[path] {
|
||||
return "", false
|
||||
}
|
||||
deps := app.Deps{Path: func() string { return path }} // Navigate is nil on the server
|
||||
return vdom.RenderHTML(app.Shell(deps, app.Routes(deps))), true
|
||||
}
|
||||
|
||||
// document wraps the server-rendered inner HTML in the page shell. No whitespace
|
||||
// between <div id="app"> and the markup, so hydration's childNodes line up. The
|
||||
// dev server injects the livereload script before </body> in watch mode.
|
||||
func document(inner string) string {
|
||||
// The theme boot script comes FIRST — before the stylesheet, before any markup.
|
||||
//
|
||||
// The server cannot read localStorage, so it cannot know which theme to render. If
|
||||
// the dark class were applied by the WebAssembly once it loads, a dark-mode user
|
||||
// would be shown a white page for as long as the binary takes to download and then
|
||||
// have it snatched away. This runs synchronously, before the first paint, so the
|
||||
// first paint is already right. It is the only JavaScript in the project.
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>kjøl — a shared base layer</title>
|
||||
` + webui.ThemeBootScript + `
|
||||
<link rel="stylesheet" href="/app.css" />
|
||||
</head>
|
||||
<body class="bg-surface text-ink antialiased">
|
||||
<div id="app">` + inner + `</div>
|
||||
<script src="/wasm_exec.js"></script>
|
||||
<script src="/wasmboot.js"></script>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
Reference in New Issue
Block a user