pkg-proxy/internal/server/templates.go
Andrew Nesbitt 34009bad98
Lazy-load HTML templates behind sync.Once (#59)
Templates are parsed on first Render call instead of at server startup.
API-only traffic never pays the ~780µs parsing cost.

Closes #53
2026-04-06 13:06:25 +01:00

80 lines
1.7 KiB
Go

package server
import (
"embed"
"html/template"
"net/http"
"path/filepath"
"sync"
)
//go:embed templates/**/*.html
var templatesFS embed.FS
// Templates holds lazily-parsed templates for each page.
type Templates struct {
once sync.Once
pages map[string]*template.Template
err error
}
// load parses all templates from the embedded filesystem on first call.
func (t *Templates) load() error {
t.once.Do(func() {
pages := make(map[string]*template.Template)
funcMap := template.FuncMap{
"add": func(a, b int) int { return a + b },
"sub": func(a, b int) int { return a - b },
"supportedEcosystems": supportedEcosystems,
"ecosystemBadgeClass": ecosystemBadgeClasses,
"ecosystemBadgeLabel": ecosystemBadgeLabel,
}
pageFiles, err := templatesFS.ReadDir("templates/pages")
if err != nil {
t.err = err
return
}
for _, pageFile := range pageFiles {
if pageFile.IsDir() {
continue
}
pageName := pageFile.Name()
pageName = pageName[:len(pageName)-len(filepath.Ext(pageName))]
tmpl, err := template.New("").Funcs(funcMap).ParseFS(templatesFS,
"templates/layout/*.html",
"templates/components/*.html",
"templates/pages/"+pageFile.Name(),
)
if err != nil {
t.err = err
return
}
pages[pageName] = tmpl
}
t.pages = pages
})
return t.err
}
// Render renders a page template with the given data.
func (t *Templates) Render(w http.ResponseWriter, pageName string, data any) error {
if err := t.load(); err != nil {
return err
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
tmpl, ok := t.pages[pageName]
if !ok {
return http.ErrAbortHandler
}
return tmpl.ExecuteTemplate(w, "base", data)
}