BT  ·  2026-06-22

static.go

 1package pages
 2
 3import (
 4	"crypto/sha256"
 5	"embed"
 6	"encoding/base32"
 7	"io/fs"
 8	"log/slog"
 9	"path/filepath"
10	"strings"
11)
12
13var toHashCache map[string]string
14var fromHashCache map[string]string
15
16func init() {
17	toHashCache = make(map[string]string)
18	fromHashCache = make(map[string]string)
19}
20
21//go:embed static/* static/**/*
22var static embed.FS
23
24type fsys struct {
25	static embed.FS
26}
27
28var StaticFS fs.FS = &fsys{static: static}
29
30func (f *fsys) Open(name string) (fs.File, error) {
31	nname := unhashStatic(name)
32	if filepath.IsAbs(nname) {
33		nname = filepath.Join(".", nname)
34	}
35	file, err := f.static.Open(nname)
36	if err != nil {
37		return nil, err
38	}
39	return file, nil
40}
41
42func hashStatic(name string) string {
43	if cached, ok := toHashCache[name]; ok {
44		return cached
45	}
46	nname := filepath.Join(".", name)
47	data, err := static.ReadFile(nname)
48	if err != nil {
49		slog.Error("error reading static file", "name", nname)
50		return name
51	}
52	h := sha256.Sum256(data)
53	hash := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(h[:])
54	dir, file := filepath.Split(name)
55	fileBase := strings.TrimRight(file, filepath.Ext(file))
56	hashedName := filepath.Join(dir, fileBase+"-"+hash[0:9]+filepath.Ext(file))
57	toHashCache[name] = hashedName
58	fromHashCache[strings.TrimLeft(hashedName, "/")] = name
59	return hashedName
60}
61
62func unhashStatic(name string) string {
63	if cached, ok := fromHashCache[name]; ok {
64		return cached
65	}
66	parts := strings.Split(name, "-")
67	if len(parts) != 2 {
68		return name
69	}
70	originalName := parts[0] + filepath.Ext(name)
71	fromHashCache[name] = originalName
72	return originalName
73}
74
75var _ fs.FS = &fsys{}