serve the embedded frontend alongside the API, slice 1 of M4
//go:embed needs internal/webdist/dist to exist at compile time, so a one-line placeholder index.html is committed there and the real build (Docker, or a manual copy of web/dist) overwrites it. One mux serves /, /api, and a /healthz for the compose healthcheck to come.
This commit is contained in:
parent
bbba450c6e
commit
3684488d75
4 changed files with 58 additions and 5 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -3,3 +3,8 @@
|
||||||
|
|
||||||
# prettier cache (npm run format in web/ walks up here)
|
# prettier cache (npm run format in web/ walks up here)
|
||||||
/node_modules/
|
/node_modules/
|
||||||
|
|
||||||
|
# real frontend builds copied over the committed placeholder (index.html
|
||||||
|
# stays tracked; `git restore internal/webdist/dist` after local testing)
|
||||||
|
/internal/webdist/dist/*
|
||||||
|
!/internal/webdist/dist/index.html
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Command spreadlab serves the spreadlab API (and, from milestone 4, the
|
// Command spreadlab serves the dashboard and its API from one binary:
|
||||||
// dashboard itself). The -table flag instead prints the three-scenario
|
// the embedded frontend on /, the API under /api, and a trivial
|
||||||
// comparison and exits, a quick engine sanity check.
|
// /healthz for container healthchecks. The -table flag instead prints
|
||||||
|
// the three-scenario comparison and exits, a quick engine sanity check.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -14,6 +15,7 @@ import (
|
||||||
|
|
||||||
"github.com/JustinZeus/spreadlab/internal/api"
|
"github.com/JustinZeus/spreadlab/internal/api"
|
||||||
"github.com/JustinZeus/spreadlab/internal/engine"
|
"github.com/JustinZeus/spreadlab/internal/engine"
|
||||||
|
"github.com/JustinZeus/spreadlab/internal/webdist"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -29,8 +31,17 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("spreadlab API listening on http://%s", *addr)
|
// One mux, one origin: the longer /api/ pattern wins over / for API
|
||||||
if err := http.ListenAndServe(*addr, api.NewServer()); err != nil {
|
// calls, everything else falls through to the embedded frontend.
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/api/", api.NewServer())
|
||||||
|
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
})
|
||||||
|
mux.Handle("/", webdist.Handler())
|
||||||
|
|
||||||
|
log.Printf("spreadlab listening on http://%s", *addr)
|
||||||
|
if err := http.ListenAndServe(*addr, mux); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
internal/webdist/dist/index.html
vendored
Normal file
4
internal/webdist/dist/index.html
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<!doctype html>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>spreadlab</title>
|
||||||
|
<p>Placeholder page: this binary was built without the frontend. Run <code>npm run build</code> in <code>web/</code> and copy <code>web/dist</code> over <code>internal/webdist/dist</code>, or use the Docker image.</p>
|
||||||
33
internal/webdist/webdist.go
Normal file
33
internal/webdist/webdist.go
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Package webdist embeds the built frontend so one binary serves both
|
||||||
|
// the dashboard and the API. `//go:embed` needs its directory to exist
|
||||||
|
// at compile time, but the real build output (web/dist) is gitignored,
|
||||||
|
// so this package commits a one-line placeholder dist/index.html that
|
||||||
|
// keeps `go build` and `go test ./...` working everywhere. The
|
||||||
|
// Dockerfile overwrites the placeholder with the real `npm run build`
|
||||||
|
// output before compiling; to try that locally, copy web/dist over
|
||||||
|
// internal/webdist/dist and `git restore` it afterwards.
|
||||||
|
package webdist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The all: prefix embeds files whose names start with . or _ too, which
|
||||||
|
// plain go:embed skips; vite output can contain such assets.
|
||||||
|
//
|
||||||
|
//go:embed all:dist
|
||||||
|
var embeddedFiles embed.FS
|
||||||
|
|
||||||
|
// Handler serves the embedded frontend. The dashboard is a single page
|
||||||
|
// without client-side routes, so unknown paths get the file server's
|
||||||
|
// plain 404 rather than a fallback to index.html.
|
||||||
|
func Handler() http.Handler {
|
||||||
|
distRoot, err := fs.Sub(embeddedFiles, "dist")
|
||||||
|
if err != nil {
|
||||||
|
// Unreachable: "dist" is compiled into embeddedFiles above.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return http.FileServer(http.FS(distRoot))
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue