mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-22 21:42:50 +02:00
tpl/tplimpl: Rework template management to get rid of concurrency issues
This more or less completes the simplification of the template handling code in Hugo started in v0.62. The main motivation was to fix a long lasting issue about a crash in HTML content files without front matter. But this commit also comes with a big functional improvement. As we now have moved the base template evaluation to the build stage we now use the same lookup rules for `baseof` as for `list` etc. type of templates. This means that in this simple example you can have a `baseof` template for the `blog` section without having to duplicate the others: ``` layouts ├── _default │ ├── baseof.html │ ├── list.html │ └── single.html └── blog └── baseof.html ``` Also, when simplifying code, you often get rid of some double work, as shown in the "site building" benchmarks below. These benchmarks looks suspiciously good, but I have repeated the below with ca. the same result. Compared to master: ``` name old time/op new time/op delta SiteNew/Bundle_with_image-16 13.1ms ± 1% 10.5ms ± 1% -19.34% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 13.0ms ± 0% 10.7ms ± 1% -18.05% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 46.4ms ± 2% 43.1ms ± 1% -7.15% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 52.2ms ± 2% 47.8ms ± 1% -8.30% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 77.9ms ± 1% 70.9ms ± 1% -9.01% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 43.0ms ± 0% 37.2ms ± 1% -13.54% (p=0.029 n=4+4) SiteNew/Page_collections-16 58.2ms ± 1% 52.4ms ± 1% -9.95% (p=0.029 n=4+4) name old alloc/op new alloc/op delta SiteNew/Bundle_with_image-16 3.81MB ± 0% 2.22MB ± 0% -41.70% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 3.60MB ± 0% 2.01MB ± 0% -44.20% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 19.3MB ± 1% 14.1MB ± 0% -26.91% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 70.7MB ± 0% 69.0MB ± 0% -2.40% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 37.1MB ± 0% 31.2MB ± 0% -15.94% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 17.6MB ± 0% 10.6MB ± 0% -39.92% (p=0.029 n=4+4) SiteNew/Page_collections-16 25.9MB ± 0% 21.2MB ± 0% -17.99% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Bundle_with_image-16 52.3k ± 0% 26.1k ± 0% -50.18% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 52.3k ± 0% 26.1k ± 0% -50.16% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 336k ± 1% 269k ± 0% -19.90% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 422k ± 0% 395k ± 0% -6.43% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 401k ± 0% 313k ± 0% -21.79% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 247k ± 0% 143k ± 0% -42.17% (p=0.029 n=4+4) SiteNew/Page_collections-16 282k ± 0% 207k ± 0% -26.55% (p=0.029 n=4+4) ``` Fixes #6716 Fixes #6760 Fixes #6768 Fixes #6778
This commit is contained in:
@@ -17,10 +17,9 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
|
||||
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
|
||||
htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
|
||||
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
||||
|
||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
@@ -41,25 +40,21 @@ type templateContext struct {
|
||||
visited map[string]bool
|
||||
templateNotFound map[string]bool
|
||||
identityNotFound map[string]bool
|
||||
lookupFn func(name string) *templateInfoTree
|
||||
lookupFn func(name string) *templateState
|
||||
|
||||
// The last error encountered.
|
||||
err error
|
||||
|
||||
typ templateType
|
||||
|
||||
// Set when we're done checking for config header.
|
||||
configChecked bool
|
||||
|
||||
// Contains some info about the template
|
||||
parseInfo *tpl.ParseInfo
|
||||
id identity.Manager
|
||||
t *templateState
|
||||
|
||||
// Store away the return node in partials.
|
||||
returnNode *parse.CommandNode
|
||||
}
|
||||
|
||||
func (c templateContext) getIfNotVisited(name string) *templateInfoTree {
|
||||
func (c templateContext) getIfNotVisited(name string) *templateState {
|
||||
if c.visited[name] {
|
||||
return nil
|
||||
}
|
||||
@@ -76,13 +71,11 @@ func (c templateContext) getIfNotVisited(name string) *templateInfoTree {
|
||||
}
|
||||
|
||||
func newTemplateContext(
|
||||
id identity.Manager,
|
||||
info *tpl.ParseInfo,
|
||||
lookupFn func(name string) *templateInfoTree) *templateContext {
|
||||
t *templateState,
|
||||
lookupFn func(name string) *templateState) *templateContext {
|
||||
|
||||
return &templateContext{
|
||||
id: id,
|
||||
parseInfo: info,
|
||||
t: t,
|
||||
lookupFn: lookupFn,
|
||||
visited: make(map[string]bool),
|
||||
templateNotFound: make(map[string]bool),
|
||||
@@ -90,79 +83,36 @@ func newTemplateContext(
|
||||
}
|
||||
}
|
||||
|
||||
func createGetTemplateInfoTreeFor(getID func(name string) *templateInfoTree) func(nn string) *templateInfoTree {
|
||||
return func(nn string) *templateInfoTree {
|
||||
return getID(nn)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *templateHandler) applyTemplateTransformersToHMLTTemplate(typ templateType, templ *template.Template) (*templateContext, error) {
|
||||
id, info := t.createTemplateInfo(templ.Name())
|
||||
ti := &templateInfoTree{
|
||||
tree: templ.Tree,
|
||||
templ: templ,
|
||||
typ: typ,
|
||||
id: id,
|
||||
info: info,
|
||||
}
|
||||
t.templateInfoTree[templ.Name()] = ti
|
||||
getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
|
||||
return t.templateInfoTree[name]
|
||||
})
|
||||
|
||||
return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
|
||||
}
|
||||
|
||||
func (t *templateHandler) applyTemplateTransformersToTextTemplate(typ templateType, templ *texttemplate.Template) (*templateContext, error) {
|
||||
id, info := t.createTemplateInfo(templ.Name())
|
||||
ti := &templateInfoTree{
|
||||
tree: templ.Tree,
|
||||
templ: templ,
|
||||
typ: typ,
|
||||
id: id,
|
||||
info: info,
|
||||
}
|
||||
|
||||
t.templateInfoTree[templ.Name()] = ti
|
||||
getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
|
||||
return t.templateInfoTree[name]
|
||||
})
|
||||
|
||||
return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
|
||||
|
||||
}
|
||||
|
||||
type templateInfoTree struct {
|
||||
info tpl.ParseInfo
|
||||
typ templateType
|
||||
id identity.Manager
|
||||
templ tpl.Template
|
||||
tree *parse.Tree
|
||||
}
|
||||
|
||||
func applyTemplateTransformers(
|
||||
typ templateType,
|
||||
templ *templateInfoTree,
|
||||
lookupFn func(name string) *templateInfoTree) (*templateContext, error) {
|
||||
t *templateState,
|
||||
lookupFn func(name string) *templateState) (*templateContext, error) {
|
||||
|
||||
if templ == nil {
|
||||
if t == nil {
|
||||
return nil, errors.New("expected template, but none provided")
|
||||
}
|
||||
|
||||
c := newTemplateContext(templ.id, &templ.info, lookupFn)
|
||||
c.typ = typ
|
||||
c := newTemplateContext(t, lookupFn)
|
||||
tree := getParseTree(t.Template)
|
||||
|
||||
_, err := c.applyTransformations(templ.tree.Root)
|
||||
_, err := c.applyTransformations(tree.Root)
|
||||
|
||||
if err == nil && c.returnNode != nil {
|
||||
// This is a partial with a return statement.
|
||||
c.parseInfo.HasReturn = true
|
||||
templ.tree.Root = c.wrapInPartialReturnWrapper(templ.tree.Root)
|
||||
c.t.parseInfo.HasReturn = true
|
||||
tree.Root = c.wrapInPartialReturnWrapper(tree.Root)
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func getParseTree(templ tpl.Template) *parse.Tree {
|
||||
templ = unwrap(templ)
|
||||
if text, ok := templ.(*texttemplate.Template); ok {
|
||||
return text.Tree
|
||||
}
|
||||
return templ.(*htmltemplate.Template).Tree
|
||||
}
|
||||
|
||||
const (
|
||||
partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ with .Arg }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
|
||||
)
|
||||
@@ -215,7 +165,7 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
|
||||
case *parse.TemplateNode:
|
||||
subTempl := c.getIfNotVisited(x.Name)
|
||||
if subTempl != nil {
|
||||
c.applyTransformationsToNodes(subTempl.tree.Root)
|
||||
c.applyTransformationsToNodes(getParseTree(subTempl.Template).Root)
|
||||
}
|
||||
case *parse.PipeNode:
|
||||
c.collectConfig(x)
|
||||
@@ -263,7 +213,7 @@ func (c *templateContext) hasIdent(idents []string, ident string) bool {
|
||||
// on the form:
|
||||
// {{ $_hugo_config:= `{ "version": 1 }` }}
|
||||
func (c *templateContext) collectConfig(n *parse.PipeNode) {
|
||||
if c.typ != templateShortcode {
|
||||
if c.t.typ != templateShortcode {
|
||||
return
|
||||
}
|
||||
if c.configChecked {
|
||||
@@ -295,7 +245,7 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) {
|
||||
c.err = errors.Wrap(err, errMsg)
|
||||
return
|
||||
}
|
||||
if err := mapstructure.WeakDecode(m, &c.parseInfo.Config); err != nil {
|
||||
if err := mapstructure.WeakDecode(m, &c.t.parseInfo.Config); err != nil {
|
||||
c.err = errors.Wrap(err, errMsg)
|
||||
}
|
||||
}
|
||||
@@ -304,10 +254,10 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) {
|
||||
// collectInner determines if the given CommandNode represents a
|
||||
// shortcode call to its .Inner.
|
||||
func (c *templateContext) collectInner(n *parse.CommandNode) {
|
||||
if c.typ != templateShortcode {
|
||||
if c.t.typ != templateShortcode {
|
||||
return
|
||||
}
|
||||
if c.parseInfo.IsInner || len(n.Args) == 0 {
|
||||
if c.t.parseInfo.IsInner || len(n.Args) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -321,7 +271,7 @@ func (c *templateContext) collectInner(n *parse.CommandNode) {
|
||||
}
|
||||
|
||||
if c.hasIdent(idents, "Inner") {
|
||||
c.parseInfo.IsInner = true
|
||||
c.t.parseInfo.IsInner = true
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -351,8 +301,9 @@ func (c *templateContext) collectPartialInfo(x *parse.CommandNode) {
|
||||
}
|
||||
partialName = "partials/" + partialName
|
||||
info := c.lookupFn(partialName)
|
||||
|
||||
if info != nil {
|
||||
c.id.Add(info.id)
|
||||
c.t.Add(info)
|
||||
} else {
|
||||
// Delay for later
|
||||
c.identityNotFound[partialName] = true
|
||||
@@ -361,7 +312,7 @@ func (c *templateContext) collectPartialInfo(x *parse.CommandNode) {
|
||||
}
|
||||
|
||||
func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
|
||||
if c.typ != templatePartial || c.returnNode != nil {
|
||||
if c.t.typ != templatePartial || c.returnNode != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -381,3 +332,18 @@ func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
func findTemplateIn(name string, in tpl.Template) (tpl.Template, bool) {
|
||||
in = unwrap(in)
|
||||
if text, ok := in.(*texttemplate.Template); ok {
|
||||
if templ := text.Lookup(name); templ != nil {
|
||||
return templ, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
if templ := in.(*htmltemplate.Template).Lookup(name); templ != nil {
|
||||
return templ, true
|
||||
}
|
||||
return nil, false
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user