Description
Go's template packages (text/template
, html/template
) do in general work really well. But there are some issues that are hard/impossible to work around.
In the current implementation, a Template
struct is mirrored in both packages, and while there is some separation of parsing and execution to allow for concurrent execution, it is very much intermingled.
This proposal suggests adding a new "template executer" which would:
- Prepare and execute the template
- Store and resolve any template functions
- Provide some user-defined hook interface to allow for a custom evaluation of method/func/map lookups etc.
The above may look like different things, candidates for different proposals. But these issues are connected and I'm convinced that it helps to look at them as a whole, even if this proposal is only partly accepted.
The benefits of the above would be:
- Stateful functions. One common example would be a
i18n
translation func. You could make it a method ({{ .I18n "hello" }}
) or make it take the language as the first argument ({{ i18n .Language "hello" }}
), neither very practical. The current workaround is to clone the template set for each language, which is both resource wasteful and hard to get right in the non-trivial setups. I assume that these functions are added early to get parse-time signature validation, a good thing, but I don't see a reason why not to use another set for execution, making the function lookup lock-free a great performance bonus. - Some examples include case insensitive map lookups, adding some execution context to methods that support it (proposal: text/template, html/template: add ExecuteContext methods #31107), allow range of funcs that return map/slice (text/template: Methods vs funcs discrepancy #20503), allow a custom variant of the built-in and rather opinionated version of
IsTrue
(text/template: allow callers to override IsTrue #28391)
Doing this also removes some performance bottlenecks. A relevant benchmark in Hugo before/after we made these changes:
name old time/op new time/op delta
SiteNew/Many_HTML_templates-16 55.6ms ± 2% 42.9ms ± 1% -22.81% (p=0.008 n=5+5)
name old alloc/op new alloc/op delta
SiteNew/Many_HTML_templates-16 22.5MB ± 0% 17.6MB ± 0% -21.99% (p=0.008 n=5+5)
name old allocs/op new allocs/op delta
SiteNew/Many_HTML_templates-16 341k ± 0% 247k ± 0% -27.48% (p=0.008 n=5+5)
Implementation outline
In the Hugo project we have worked around the problems outlined above, some workaround less pretty than others. But recently we stumbled into a hurdle we didn't find a way around, so we closed our eyes and created a scripted fork of the text/template
and html/template
packages. We will pull in upstream fixes, and the ultimate goal is for that fork to go away.
But this means that there is a working implementation of this proposal. The implementation is a little bit coloured by trying to do the least amount of changes to the existing code, but I think it outlines a possible API. And it is backwards compatible.
// Executer executes a given template.
type Executer interface {
Execute(p Preparer, wr io.Writer, data interface{}) error
}
// Preparer prepares the template before execution.
type Preparer interface {
Prepare() (*Template, error)
}
// ExecHelper allows some custom eval hooks.
type ExecHelper interface {
GetFunc(tmpl Preparer, name string) (reflect.Value, bool)
GetMethod(tmpl Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value)
GetMapValue(tmpl Preparer, receiver, key reflect.Value) (reflect.Value, bool)
IsTrue(val reflect.Value) bool
}
Hugo's fork can be found here https://github.com/gohugoio/hugo/tree/master/tpl/internal/go_templates (all the patches are placed in hugo_*.go
files). Changes are marked with the comment Added for Hugo.
. The script that maintains the fork can be found at https://github.com/gohugoio/hugo/tree/master/scripts/fork_go_templates.
Some related Go issues
- Add ExecuteContext methods proposal: text/template, html/template: add ExecuteContext methods #31107
- Document ability to modify FuncMap after template parse by calling Funcs again html/template: document ability to modify FuncMap after template parse by calling Funcs again #34680
- Allow callers to override IsTrue text/template: allow callers to override IsTrue #28391
- Methods vs funcs discrepancy text/template: Methods vs funcs discrepancy #20503
- index should return nil instead of index out of range error text/template: index should return nil instead of index out of range error #14751
Metadata
Metadata
Assignees
Type
Projects
Status