diff --git a/assets/assets.go b/assets/assets.go
new file mode 100644
index 0000000..837ab85
--- /dev/null
+++ b/assets/assets.go
@@ -0,0 +1,5 @@
+// Package assets provides static file serving with build-tag switching
+// between live filesystem (dev) and embedded hashfs (prod).
+package assets
+
+const DirectoryPath = "assets"
diff --git a/assets/static_dev.go b/assets/static_dev.go
new file mode 100644
index 0000000..4b4776c
--- /dev/null
+++ b/assets/static_dev.go
@@ -0,0 +1,22 @@
+//go:build dev
+
+package assets
+
+import (
+ "net/http"
+ "os"
+
+ "github.com/rs/zerolog/log"
+)
+
+func Handler() http.Handler {
+ log.Debug().Str("path", DirectoryPath).Msg("static assets served from filesystem")
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Cache-Control", "no-store")
+ http.StripPrefix("/assets/", http.FileServerFS(os.DirFS(DirectoryPath))).ServeHTTP(w, r)
+ })
+}
+
+func StaticPath(path string) string {
+ return "/assets/" + path
+}
diff --git a/assets/static_prod.go b/assets/static_prod.go
new file mode 100644
index 0000000..6a17b54
--- /dev/null
+++ b/assets/static_prod.go
@@ -0,0 +1,26 @@
+//go:build !dev
+
+package assets
+
+import (
+ "embed"
+ "net/http"
+
+ "github.com/benbjohnson/hashfs"
+ "github.com/rs/zerolog/log"
+)
+
+var (
+ //go:embed css js
+ staticFiles embed.FS
+ staticSys = hashfs.NewFS(staticFiles)
+)
+
+func Handler() http.Handler {
+ log.Debug().Msg("static assets are embedded with hashfs")
+ return hashfs.FileServer(staticSys)
+}
+
+func StaticPath(path string) string {
+ return "/" + staticSys.HashName(path)
+}
diff --git a/features/common/components/shared.templ b/features/common/components/shared.templ
index d310227..b9f9d2d 100644
--- a/features/common/components/shared.templ
+++ b/features/common/components/shared.templ
@@ -51,8 +51,8 @@ templ NicknamePrompt(returnPath string) {
// LiveClock shows the current server time, updated with each SSE patch.
// If the clock stops updating, users know the connection is stale.
templ LiveClock() {
-
-
+
+
{ time.Now().Format("15:04:05") }
}
diff --git a/features/common/layouts/base.templ b/features/common/layouts/base.templ
index 5029e7f..e779665 100644
--- a/features/common/layouts/base.templ
+++ b/features/common/layouts/base.templ
@@ -1,6 +1,7 @@
package layouts
import (
+ "github.com/ryanhamamura/games/assets"
"github.com/ryanhamamura/games/config"
"github.com/ryanhamamura/games/version"
)
@@ -11,8 +12,8 @@ templ Base(title string) {
{ title }
-
-
+
+
if config.Global.Environment == config.Dev {
diff --git a/go.mod b/go.mod
index ad1794a..e7ffb13 100644
--- a/go.mod
+++ b/go.mod
@@ -68,6 +68,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
+ github.com/benbjohnson/hashfs v0.2.2 // indirect
github.com/bep/godartsass/v2 v2.5.0 // indirect
github.com/bep/golibsass v1.2.0 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
diff --git a/go.sum b/go.sum
index 150f2e4..eeb1ae1 100644
--- a/go.sum
+++ b/go.sum
@@ -136,6 +136,8 @@ github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/benbjohnson/hashfs v0.2.2 h1:vFZtksphM5LcnMRFctj49jCUkCc7wp3NP6INyfjkse4=
+github.com/benbjohnson/hashfs v0.2.2/go.mod h1:7OMXaMVo1YkfiIPxKrl7OXkUTUgWjmsAKyR+E6xDIRM=
github.com/bep/clocks v0.5.0 h1:hhvKVGLPQWRVsBP/UB7ErrHYIO42gINVbvqxvYTPVps=
github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
diff --git a/main.go b/main.go
index 34cde03..9ad9f0b 100644
--- a/main.go
+++ b/main.go
@@ -2,7 +2,6 @@ package main
import (
"context"
- "embed"
"fmt"
"log/slog"
"net"
@@ -29,9 +28,6 @@ import (
"github.com/ryanhamamura/games/version"
)
-//go:embed assets
-var assets embed.FS
-
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
@@ -97,7 +93,7 @@ func run(ctx context.Context) error {
sessionManager.LoadAndSave,
)
- router.SetupRoutes(r, queries, sessionManager, nc, store, snakeStore, assets)
+ router.SetupRoutes(r, queries, sessionManager, nc, store, snakeStore)
// HTTP server
srv := &http.Server{
diff --git a/router/router.go b/router/router.go
index 216509f..083c7ac 100644
--- a/router/router.go
+++ b/router/router.go
@@ -2,8 +2,6 @@
package router
import (
- "embed"
- "io/fs"
"net/http"
"sync"
@@ -12,6 +10,7 @@ import (
"github.com/nats-io/nats.go"
"github.com/starfederation/datastar-go/datastar"
+ "github.com/ryanhamamura/games/assets"
"github.com/ryanhamamura/games/config"
"github.com/ryanhamamura/games/connect4"
"github.com/ryanhamamura/games/db/repository"
@@ -31,11 +30,9 @@ func SetupRoutes(
nc *nats.Conn,
store *connect4.Store,
snakeStore *snake.SnakeStore,
- assets embed.FS,
) {
// Static assets
- subFS, _ := fs.Sub(assets, "assets")
- router.Handle("/assets/*", http.StripPrefix("/assets/", http.FileServerFS(subFS)))
+ router.Handle("/assets/*", assets.Handler())
// Hot-reload for development
if config.Global.Environment == config.Dev {