repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

2.4 KB · 88 lines · Go Raw History
 1// Package web holds the HTTP pieces every site in this repo needs: resolving
 2// Vite's content-hashed filenames, request logging, security headers, and a
 3// server that shuts down cleanly.
 4package web
 5
 6import (
 7	"encoding/json"
 8	"fmt"
 9	"io/fs"
10	"sync"
11)
12
13// Assets resolves a Vite entry name to the content-hashed files Vite emitted,
14// by reading dist/.vite/manifest.json.
15type Assets struct {
16	entries map[string]manifestEntry
17	once    sync.Once
18}
19
20type manifestEntry struct {
21	File    string   `json:"file"`
22	Src     string   `json:"src"`
23	IsEntry bool     `json:"isEntry"`
24	CSS     []string `json:"css"`
25	Assets  []string `json:"assets"`
26}
27
28// LoadAssets reads the manifest out of a dist filesystem. A missing manifest is
29// an error rather than a warning, because serving a page whose script tag points
30// at a file that was never built is worse than refusing to start.
31func LoadAssets(dist fs.FS) (*Assets, error) {
32	raw, err := fs.ReadFile(dist, ".vite/manifest.json")
33	if err != nil {
34		return nil, fmt.Errorf("read vite manifest: %w (did you run `make build` in frontend/?)", err)
35	}
36
37	var entries map[string]manifestEntry
38	if err := json.Unmarshal(raw, &entries); err != nil {
39		return nil, fmt.Errorf("parse vite manifest: %w", err)
40	}
41
42	return &Assets{entries: entries}, nil
43}
44
45// Script returns the public URL of the JS bundle for a Vite entry, e.g.
46// Script("index.js") -> "/static/base-aDClsiBF.js".
47func (a *Assets) Script(entry string) string {
48	e, ok := a.entries[entry]
49	if !ok {
50		return ""
51	}
52	return "/static/" + e.File
53}
54
55// Hashed reports every file Vite emitted with a content hash in its name, which
56// is the set safe to cache for a year. Files copied through from publicDir keep
57// their original names and stay out of it, or an updated resume would never
58// reach anybody.
59func (a *Assets) Hashed() map[string]bool {
60	hashed := make(map[string]bool, len(a.entries)*2)
61	for _, e := range a.entries {
62		if e.File != "" {
63			hashed[e.File] = true
64		}
65		for _, css := range e.CSS {
66			hashed[css] = true
67		}
68		for _, asset := range e.Assets {
69			hashed[asset] = true
70		}
71	}
72	return hashed
73}
74
75// Styles returns the public URLs of every stylesheet Vite extracted from an
76// entry. Plural because a code-split entry can pull in more than one.
77func (a *Assets) Styles(entry string) []string {
78	e, ok := a.entries[entry]
79	if !ok {
80		return nil
81	}
82	out := make([]string, 0, len(e.CSS))
83	for _, css := range e.CSS {
84		out = append(out, "/static/"+css)
85	}
86	return out
87}