Fix the logo's pop-in with a traced vector logo
The logo was an 880 KB SVG wrapping a 1254px PNG, downloaded again on every page load. Keep that PNG as the master (artwork/logo.png) and have scripts/logo.py trace it with potrace into a 6 KB logo.svg (favicon, README) and logo.js, which brand.js draws inline in the header and on the login page so the logo arrives with the page. Remove the unused logo-white.svg. Static files now carry content ETags, so reloads get 304s instead of downloading the UI again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf49df0346
commit
0f04dbb4b1
15 files changed
+215
-23049
No files matched your search
@@ -1,10 +1,14 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"jarl/internal/api"
|
||||
"jarl/internal/auth"
|
||||
@@ -120,6 +124,7 @@ func (s *Server) routes() {
|
||||
// Web UI
|
||||
if s.web != nil {
|
||||
files := http.FileServer(http.FS(s.web))
|
||||
etags := contentETags(s.web)
|
||||
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
w.Header().Set("Allow", "GET, HEAD")
|
||||
@@ -134,11 +139,17 @@ func (s *Server) routes() {
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Write(b)
|
||||
w.Header().Set("ETag", etags["index.html"])
|
||||
http.ServeContent(w, r, "index.html", time.Time{}, bytes.NewReader(b))
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(r.URL.Path, "/assets/") {
|
||||
// no-cache: the browser asks again each time, but with
|
||||
// the ETag an unchanged file costs a 304 and no body.
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
if tag, ok := etags[strings.TrimPrefix(r.URL.Path, "/")]; ok {
|
||||
w.Header().Set("ETag", tag)
|
||||
}
|
||||
files.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
@@ -233,3 +244,22 @@ func require(p *auth.Principal, a auth.Action) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// contentETags returns an ETag for every file of the embedded web UI,
|
||||
// from its content (embedded files have no modification time).
|
||||
func contentETags(fsys fs.FS) map[string]string {
|
||||
tags := map[string]string{}
|
||||
fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
b, err := fs.ReadFile(fsys, path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
sum := sha256.Sum256(b)
|
||||
tags[path] = `"` + hex.EncodeToString(sum[:12]) + `"`
|
||||
return nil
|
||||
})
|
||||
return tags
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
// Static files carry an ETag, so a browser's revalidation of an unchanged
|
||||
// file is a 304 without a body.
|
||||
func TestStaticETags(t *testing.T) {
|
||||
web := fstest.MapFS{
|
||||
"index.html": {Data: []byte("<!doctype html><title>Jarl</title>")},
|
||||
"assets/logo.js": {Data: []byte("export const x = 1;\n")},
|
||||
}
|
||||
s := &Server{cfg: Config{}, log: slog.New(slog.NewTextHandler(io.Discard, nil)), mux: http.NewServeMux(), limiter: newRateLimiter(), web: web}
|
||||
s.routes()
|
||||
for _, path := range []string{"/", "/assets/logo.js"} {
|
||||
w := httptest.NewRecorder()
|
||||
s.mux.ServeHTTP(w, httptest.NewRequest("GET", path, nil))
|
||||
tag := w.Header().Get("ETag")
|
||||
if w.Code != 200 || tag == "" || w.Body.Len() == 0 {
|
||||
t.Fatalf("%s: %d etag %q", path, w.Code, tag)
|
||||
}
|
||||
r := httptest.NewRequest("GET", path, nil)
|
||||
r.Header.Set("If-None-Match", tag)
|
||||
w = httptest.NewRecorder()
|
||||
s.mux.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusNotModified || w.Body.Len() != 0 {
|
||||
t.Errorf("%s revalidated: %d, %d bytes", path, w.Code, w.Body.Len())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user