···
46
-
t map[string]*template.Template
46
+
cache *TmplCache[string, *template.Template]
avatar config.AvatarConfig
resolver *idresolver.Resolver
···
68
-
t: make(map[string]*template.Template),
68
+
cache: NewTmplCache[string, *template.Template](),
···
logger: slog.Default().With("component", "pages"),
77
-
// Initial load of all templates
78
-
p.loadAllTemplates()
78
+
p.embedFS = os.DirFS(p.templateDir)
86
+
func (p *Pages) pathToName(s string) string {
87
+
return strings.TrimSuffix(strings.TrimPrefix(s, "templates/"), ".html")
90
+
// reverse of pathToName
91
+
func (p *Pages) nameToPath(s string) string {
92
+
return "templates/" + s + ".html"
func (p *Pages) fragmentPaths() ([]string, error) {
var fragmentPaths []string
err := fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error {
···
return fragmentPaths, nil
108
-
func (p *Pages) loadAllTemplates() {
110
-
p.embedFS = os.DirFS(p.templateDir)
115
-
l := p.logger.With("handler", "loadAllTemplates")
116
-
templates := make(map[string]*template.Template)
120
+
func (p *Pages) fragments() (*template.Template, error) {
fragmentPaths, err := p.fragmentPaths()
119
-
l.Error("failed to collect fragments", "err", err)
126
+
funcs := p.funcMap()
// parse all fragments together
124
-
allFragments := template.New("").Funcs(p.funcMap())
129
+
allFragments := template.New("").Funcs(funcs)
for _, f := range fragmentPaths {
126
-
name := strings.TrimPrefix(f, "templates/")
127
-
name = strings.TrimSuffix(name, ".html")
128
-
pf, err := template.New(name).Funcs(p.funcMap()).ParseFS(p.embedFS, f)
131
+
name := p.pathToName(f)
133
+
pf, err := template.New(name).
135
+
ParseFS(p.embedFS, f)
130
-
l.Error("failed to parse fragment", "name", name, "path", f)
allFragments, err = allFragments.AddParseTree(name, pf.Tree)
135
-
l.Error("failed to add parse tree", "name", name, "path", f)
138
-
templates[name] = allFragments.Lookup(name)
140
-
// Then walk through and setup the rest of the templates
141
-
err = fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error {
148
-
if !strings.HasSuffix(path, "html") {
151
-
// Skip fragments as they've already been loaded
152
-
if strings.Contains(path, "fragments/") {
156
-
if strings.Contains(path, "layouts/") {
159
-
name := strings.TrimPrefix(path, "templates/")
160
-
name = strings.TrimSuffix(name, ".html")
161
-
// Add the page template on top of the base
162
-
allPaths := []string{}
163
-
allPaths = append(allPaths, "templates/layouts/*.html")
164
-
allPaths = append(allPaths, fragmentPaths...)
165
-
allPaths = append(allPaths, path)
166
-
tmpl, err := template.New(name).
167
-
Funcs(p.funcMap()).
168
-
ParseFS(p.embedFS, allPaths...)
170
-
return fmt.Errorf("setting up template: %w", err)
172
-
templates[name] = tmpl
173
-
l.Debug("loaded all templates")
146
+
return allFragments, nil
149
+
// parse without memoization
150
+
func (p *Pages) rawParse(stack ...string) (*template.Template, error) {
151
+
paths, err := p.fragmentPaths()
177
-
l.Error("walking template dir", "err", err)
155
+
for _, s := range stack {
156
+
paths = append(paths, p.nameToPath(s))
181
-
l.Info("loaded all templates", "total", len(templates))
183
-
defer p.mu.Unlock()
159
+
funcs := p.funcMap()
160
+
top := stack[len(stack)-1]
161
+
parsed, err := template.New(top).
163
+
ParseFS(p.embedFS, paths...)
187
-
func (p *Pages) executeOrReload(templateName string, w io.Writer, base string, params any) error {
188
-
// In dev mode, reparse templates from disk before executing
190
-
p.loadAllTemplates()
171
+
func (p *Pages) parse(stack ...string) (*template.Template, error) {
172
+
key := strings.Join(stack, "|")
174
+
// never cache in dev mode
175
+
if cached, exists := p.cache.Get(key); !p.dev && exists {
194
-
defer p.mu.RUnlock()
195
-
tmpl, exists := p.t[templateName]
197
-
return fmt.Errorf("template not found: %s", templateName)
179
+
result, err := p.rawParse(stack...)
201
-
return tmpl.Execute(w, params)
203
-
return tmpl.ExecuteTemplate(w, base, params)
184
+
p.cache.Set(key, result)
188
+
func (p *Pages) parseBase(top string) (*template.Template, error) {
193
+
return p.parse(stack...)
207
-
func (p *Pages) execute(name string, w io.Writer, params any) error {
208
-
return p.executeOrReload(name, w, "layouts/base", params)
196
+
func (p *Pages) parseRepoBase(top string) (*template.Template, error) {
199
+
"layouts/repobase",
202
+
return p.parse(stack...)
func (p *Pages) executePlain(name string, w io.Writer, params any) error {
212
-
return p.executeOrReload(name, w, "", params)
206
+
tpl, err := p.parse(name)
211
+
return tpl.Execute(w, params)
214
+
func (p *Pages) execute(name string, w io.Writer, params any) error {
215
+
tpl, err := p.parseBase(name)
220
+
return tpl.ExecuteTemplate(w, "layouts/base", params)
func (p *Pages) executeRepo(name string, w io.Writer, params any) error {
216
-
return p.executeOrReload(name, w, "layouts/repobase", params)
224
+
tpl, err := p.parseRepoBase(name)
229
+
return tpl.ExecuteTemplate(w, "layouts/base", params)
func (p *Pages) Favicon(w io.Writer) error {