package ssr import ( "errors" "html/template" "io" "sync" "github.com/cespare/xxhash/v2" ) type Writer struct { err error w io.Writer cache *TmplCache } func NewWriter(w io.Writer, cache *TmplCache) *Writer { return &Writer{w: w, cache: cache} } func (w *Writer) Raw(s string) error { _, err := w.w.Write([]byte(s)) w.err = errors.Join(w.err, err) return w.err } func (w *Writer) Tmpl(data any, tmpl string) error { t, err := w.cache.GetOrCompile(tmpl) if err != nil { w.err = errors.Join(w.err, err) return w.err } err = t.Execute(w.w, data) w.err = errors.Join(w.err, err) return w.err } func (w *Writer) Error() error { return w.err } type TmplCache struct { funcs template.FuncMap cache sync.Map // uint64 -> *template.Template } func NewTmplCache(funcs template.FuncMap) *TmplCache { return &TmplCache{funcs: funcs} } func (t *TmplCache) GetOrCompile(content string) (*template.Template, error) { key := xxhash.Sum64String(content) if cached, ok := t.cache.Load(key); ok { return cached.(*template.Template), nil } tmpl, err := template.New("").Funcs(t.funcs).Parse(content) if err != nil { return nil, err } t.cache.Store(key, tmpl) return tmpl, nil } type Renderable interface { Render(sw *Writer) error }