I'm looking to see if there is a better (faster, more organised) way to split up my templates in Go. I strongly prefer to stick to html/template (or a wrapper thereof) since I trust its security model.
template.ParseGlob
to parse all of my template files in within init()
.$title
in each template (i.e. listing_payment.tmpl
) and pass this to the content template.html/template
caches templates in memory once parsedt.ExecuteTemplate(w, "name.tmpl", map[string]interface{})
and don't do any silly parsing on each request.I compose templates from multiple pieces (and this is the bit I find clunky) as below:
{{ $title := "Page Title" }}
{{ template "head" $title }}
{{ template "checkout" }}
{{ template "top" }}
{{ template "sidebar_details" . }}
{{ template "sidebar_payments" }}
{{ template "sidebar_bottom" }}
<div class="bordered-content">
...
{{ template "listing_content" . }}
...
</div>
{{ template "footer"}}
{{ template "bottom" }}
My three questions are:
Is this performant, or do the multiple {{ template "name" }}
tags result in a potential per-request performance hit? I see a lot of write - broken pipe
errors when stress testing heavier pages. This might just be due to socket timeouts (i.e. socket closing before the writer can finish) rather than some kind of per-request composition, though (correct me if otherwise!)
Is there a better way to do this within the constraints of the html/template package? The first example in Django's template docs approaches what I'd like. Extend a base layout and replace the title, sidebar and content blocks as needed.
Somewhat tangential: when template.ExecuteTemplate returns an error during a request, is there an idiomatic way to handle it? If I pass the writer to an error handler I end up with soup on the page (because it just continues writing), but a re-direct doesn't seem like idiomatic HTTP.
With some help on Reddit I managed to work out a fairly sensible (and performant) approach to this that allows:
base.tmpl
<html>
<head>
{{ template "title" .}}
</head>
<body>
{{ template "scripts" . }}
{{ template "sidebar" . }}
{{ template "content" . }}
<footer>
...
</footer>
</body>
index.tmpl
{{ define "title"}}<title>Index Page</title>{{ end }}
// We must define every block in the base layout.
{{ define "scripts" }} {{ end }}
{{ define "sidebar" }}
// We have a two part sidebar that changes depending on the page
{{ template "sidebar_index" }}
{{ template "sidebar_base" }}
{{ end }}
{{ define "content" }}
{{ template "listings_table" . }}
{{ end }}
... and our Go code, which leverages the map[string]*template.Template
approach outlined in this SO answer:
var templates map[string]*template.Template
var ErrTemplateDoesNotExist = errors.New("The template does not exist.")
// Load templates on program initialisation
func init() {
if templates == nil {
templates = make(map[string]*template.Template)
}
templates["index.html"] = template.Must(template.ParseFiles("index.tmpl", "sidebar_index.tmpl", "sidebar_base.tmpl", "listings_table.tmpl", "base.tmpl"))
...
}
// renderTemplate is a wrapper around template.ExecuteTemplate.
func renderTemplate(w http.ResponseWriter, name string, data map[string]interface{}) error {
// Ensure the template exists in the map.
tmpl, ok := templates[name]
if !ok {
return ErrTemplateDoesNotExist
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
tmpl.ExecuteTemplate(w, "base", data)
return nil
}
From initial benchmarks (using wrk
) it seems to be a fair bit more performant when it comes to heavy load, likely due to the fact that we're not passing around a whole ParseGlob
worth of templates every request. It also makes authoring the templates themselves a lot simpler.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With