Make HTML template functions support context (#24056)
# Background Golang template is not friendly for large projects, and Golang template team is quite slow, related: * `https://github.com/golang/go/issues/54450` Without upstream support, we can also have our solution to make HTML template functions support context. It helps a lot, the above Golang template issue `#54450` explains a lot: 1. It makes `{{Locale.Tr}}` could be used in any template, without passing unclear `(dict "root" . )` anymore. 2. More and more functions need `context`, like `avatar`, etc, we do not need to do `(dict "Context" $.Context)` anymore. 3. Many request-related functions could be shared by parent&children templates, like "user setting" / "system setting" See the test `TestScopedTemplateSetFuncMap`, one template set, two `Execute` calls with different `CtxFunc`. # The Solution Instead of waiting for upstream, this PR re-uses the escaped HTML template trees, use `AddParseTree` to add related templates/trees to a new template instance, then the new template instance can have its own FuncMap , the function calls in the template trees will always use the new template's FuncMap. `template.New` / `template.AddParseTree` / `adding-FuncMap` are all quite fast, so the performance is not affected. The details: 1. Make a new `html/template/Template` for `all` templates 2. Add template code to the `all` template 3. Freeze the `all` template, reset its exec func map, it shouldn't execute any template. 4. When a router wants to render a template by its `name` 1. Find the `name` in `all` 2. Find all its related sub templates 3. Escape all related templates (just like what the html template package does) 4. Add the escaped parse-trees of related templates into a new (scoped) `text/template/Template` 5. Add context-related func map into the new (scoped) text template 6. Execute the new (scoped) text template 7. To improve performance, the escaped templates are cached to `template sets` # FAQ ## There is a `unsafe` call, is this PR unsafe? This PR is safe. Golang has strict language definition, it's safe to do so: https://pkg.go.dev/unsafe#Pointer (1) Conversion of a *T1 to Pointer to *T2 ## What if Golang template supports such feature in the future? The public structs/interfaces/functions introduced by this PR is quite simple, the code of `HTMLRender` is not changed too much. It's very easy to switch to the official mechanism if there would be one. ## Does this PR change the template execution behavior? No, see the tests (welcome to design more tests if it's necessary) --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Jason Song <i@wolfogre.com> Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
parent
de2268ffab
commit
722dab5286
5 changed files with 351 additions and 16 deletions
|
@ -9,7 +9,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
@ -22,13 +21,16 @@ import (
|
|||
"code.gitea.io/gitea/modules/assetfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates/scopedtmpl"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
var rendererKey interface{} = "templatesHtmlRenderer"
|
||||
|
||||
type TemplateExecutor scopedtmpl.TemplateExecutor
|
||||
|
||||
type HTMLRender struct {
|
||||
templates atomic.Pointer[template.Template]
|
||||
templates atomic.Pointer[scopedtmpl.ScopedTemplate]
|
||||
}
|
||||
|
||||
var ErrTemplateNotInitialized = errors.New("template system is not initialized, check your log for errors")
|
||||
|
@ -47,22 +49,20 @@ func (h *HTMLRender) HTML(w io.Writer, status int, name string, data interface{}
|
|||
return t.Execute(w, data)
|
||||
}
|
||||
|
||||
func (h *HTMLRender) TemplateLookup(name string) (*template.Template, error) {
|
||||
func (h *HTMLRender) TemplateLookup(name string) (TemplateExecutor, error) {
|
||||
tmpls := h.templates.Load()
|
||||
if tmpls == nil {
|
||||
return nil, ErrTemplateNotInitialized
|
||||
}
|
||||
tmpl := tmpls.Lookup(name)
|
||||
if tmpl == nil {
|
||||
return nil, util.ErrNotExist
|
||||
}
|
||||
return tmpl, nil
|
||||
|
||||
return tmpls.Executor(name, NewFuncMap()[0])
|
||||
}
|
||||
|
||||
func (h *HTMLRender) CompileTemplates() error {
|
||||
extSuffix := ".tmpl"
|
||||
tmpls := template.New("")
|
||||
assets := AssetFS()
|
||||
extSuffix := ".tmpl"
|
||||
tmpls := scopedtmpl.NewScopedTemplate()
|
||||
tmpls.Funcs(NewFuncMap()[0])
|
||||
files, err := ListWebTemplateAssetNames(assets)
|
||||
if err != nil {
|
||||
return nil
|
||||
|
@ -73,9 +73,6 @@ func (h *HTMLRender) CompileTemplates() error {
|
|||
}
|
||||
name := strings.TrimSuffix(file, extSuffix)
|
||||
tmpl := tmpls.New(filepath.ToSlash(name))
|
||||
for _, fm := range NewFuncMap() {
|
||||
tmpl.Funcs(fm)
|
||||
}
|
||||
buf, err := assets.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -84,6 +81,7 @@ func (h *HTMLRender) CompileTemplates() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
tmpls.Freeze()
|
||||
h.templates.Store(tmpls)
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue