Refactor template render (#36438)
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package templates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
texttemplate "text/template"
|
||||
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
type pageRenderer struct {
|
||||
tmplRenderer *tmplRender
|
||||
}
|
||||
|
||||
func (r *pageRenderer) funcMap(ctx context.Context) template.FuncMap {
|
||||
pageFuncMap := NewFuncMap()
|
||||
pageFuncMap["ctx"] = func() any { return ctx }
|
||||
return pageFuncMap
|
||||
}
|
||||
|
||||
func (r *pageRenderer) funcMapDummy() template.FuncMap {
|
||||
dummyFuncMap := NewFuncMap()
|
||||
dummyFuncMap["ctx"] = func() any { return nil } // for template compilation only, no context available
|
||||
return dummyFuncMap
|
||||
}
|
||||
|
||||
func (r *pageRenderer) TemplateLookup(tmpl string, templateCtx context.Context) (TemplateExecutor, error) { //nolint:revive // we don't use ctx, only pass it to the template executor
|
||||
return r.tmplRenderer.Templates().Executor(tmpl, r.funcMap(templateCtx))
|
||||
}
|
||||
|
||||
func (r *pageRenderer) HTML(w io.Writer, status int, tplName TplName, data any, templateCtx context.Context) error { //nolint:revive // we don't use ctx, only pass it to the template executor
|
||||
name := string(tplName)
|
||||
if respWriter, ok := w.(http.ResponseWriter); ok {
|
||||
if respWriter.Header().Get("Content-Type") == "" {
|
||||
respWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
}
|
||||
respWriter.WriteHeader(status)
|
||||
}
|
||||
t, err := r.TemplateLookup(name, templateCtx)
|
||||
if err != nil {
|
||||
return texttemplate.ExecError{Name: name, Err: err}
|
||||
}
|
||||
return t.Execute(w, data)
|
||||
}
|
||||
|
||||
var PageRenderer = sync.OnceValue(func() *pageRenderer {
|
||||
rendererType := util.Iif(setting.IsProd, "static", "auto-reloading")
|
||||
log.Debug("Creating %s HTML Renderer", rendererType)
|
||||
|
||||
assetFS := AssetFS()
|
||||
tr := &tmplRender{
|
||||
collectTemplateNames: func() ([]string, error) {
|
||||
names, err := assetFS.ListAllFiles(".", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names = slices.DeleteFunc(names, func(file string) bool {
|
||||
return strings.HasPrefix(file, "mail/") || !strings.HasSuffix(file, ".tmpl")
|
||||
})
|
||||
for i, file := range names {
|
||||
names[i] = strings.TrimSuffix(file, ".tmpl")
|
||||
}
|
||||
return names, nil
|
||||
},
|
||||
readTemplateContent: func(name string) ([]byte, error) {
|
||||
return assetFS.ReadFile(name + ".tmpl")
|
||||
},
|
||||
}
|
||||
|
||||
pr := &pageRenderer{tmplRenderer: tr}
|
||||
if err := tr.recompileTemplates(pr.funcMapDummy()); err != nil {
|
||||
processStartupTemplateError(err)
|
||||
}
|
||||
|
||||
if !setting.IsProd {
|
||||
go AssetFS().WatchLocalChanges(graceful.GetManager().ShutdownContext(), func() {
|
||||
if err := tr.recompileTemplates(pr.funcMapDummy()); err != nil {
|
||||
log.Error("Template error: %v\n%s", err, log.Stack(2))
|
||||
}
|
||||
})
|
||||
}
|
||||
return pr
|
||||
})
|
||||
|
||||
func PageRendererReload() error {
|
||||
return PageRenderer().tmplRenderer.recompileTemplates(PageRenderer().funcMapDummy())
|
||||
}
|
||||
Reference in New Issue
Block a user