frontend/assets.go (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | package frontend
import (
"encoding/hex"
"fmt"
"hash/fnv"
"io"
"io/fs"
"path"
"path/filepath"
"strings"
"github.com/Southclaws/fault"
"github.com/Southclaws/fault/fmsg"
)
type Asset struct {
ETag string
Filename string
ImmutablePath string
StaticURL string
}
type AssetCollection struct {
Scripts []*Asset
Stylesheets []*Asset
ByImmutablePath map[string]*Asset
ByPath map[string]*Asset
}
func New() (*AssetCollection, error) {
a := &AssetCollection{
Scripts: []*Asset{},
Stylesheets: []*Asset{},
ByImmutablePath: make(map[string]*Asset),
ByPath: make(map[string]*Asset),
}
err := a.Rehash()
if err != nil {
return nil, err
}
return a, nil
}
func newAsset(filename string) (*Asset, error) {
file, err := Files.Open(filename)
if err != nil {
return nil, fault.Wrap(err, fmsg.Withf("could not open file %s", filename))
}
defer file.Close()
hasher := fnv.New64a()
if _, err := io.Copy(hasher, file); err != nil {
return nil, fault.Wrap(err, fmsg.Withf("could not hash file %s", filename))
}
rel, err := filepath.Rel("static", filename)
if err != nil {
return nil, fault.Wrap(err, fmsg.Withf("could not get relative path for %s", filename))
}
hash := hex.EncodeToString(hasher.Sum(nil))
return &Asset{
ETag: fmt.Sprintf(`W/"%s"`, hash),
Filename: filename,
ImmutablePath: makeImmutablePath(rel, hash),
StaticURL: "/" + rel,
}, nil
}
func makeImmutablePath(filename string, hash string) string {
ext := filepath.Ext(filename)
return path.Join("/", "assets", strings.Replace(filename, ext, "."+hash+ext, 1))
}
func (a *AssetCollection) Rehash() (err error) {
a.Scripts = nil
a.Stylesheets = nil
clear(a.ByImmutablePath)
clear(a.ByPath)
files, err := fs.Glob(Files, "static/**")
if err != nil {
return fault.Wrap(err, fmsg.With("could not glob files"))
}
for _, filename := range files {
asset, err := newAsset(filename)
if err != nil {
return err
}
switch filepath.Ext(filename) {
case ".js":
a.Scripts = append(a.Scripts, asset)
case ".css":
a.Stylesheets = append(a.Stylesheets, asset)
}
a.ByImmutablePath[asset.ImmutablePath] = asset
a.ByPath[asset.StaticURL] = asset
}
return nil
}
|