~ Fast embedded templates in Go with quicktemplate
Posted on Tue 29 Nov 2022 to ProgrammingGo is known for having a pretty extensive standard library, it's one of the things I like most about the language. This includes a templating library for generating either textual or HTML output, against a set of provided data. What with the latter being highly useful in the development of web applications wherein you would want to generate secure HTML. With embed, you can have your HTML templates bundled within the binary, meaning at deployment time you don't have to worry about managing additional files, reducing the administrative overhead. Today, I would like to demonstrate another approach to achieving this however, using a different templating language that generates Go code for generating the templates, and allows for generating minified all-in-one HTML pages with ease.
quicktemplate, is a template engine in Go. This differs from the templating engine offered by the standard library, in that each template is converted into Go code, then subsequently compiled. One added benefit of this is that the majority of bugs can be caught at compile time, reducing the number of template related bugs that can arise from incorrect data being passed through. This of course means each template is compiled into the binary. The major drawback with this approach of course being that you would need to undergo multiple compilations for the final binary, one for the templates, then another for the final program itself. Considering the speed of the qtc compiler however I don't think it's that much of a hindrance.
With quicktemplate you would define a template file, suffixed with *.qtpl
.
This is the extension that is checked for when qtc
is invoked. Each template
will then have a corresponding *.qtpl.go
file generated. The templating
language itself looks like this,
{% func Page() %}
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Personal blog</title>
</head>
<body>
Welcome to my personal blog.
</body>
</html>
{% endfunc %}
assuming the above code is in the file templates/page.qtpl
we can then call
the function from our Go code like so,
s := templates.Page()
fmt.Println(s)
or, if we wanted to send the template to an io.Writer
then we could invoke
templates.WritePage
. Each function that qtc
detects in a template file will
have a corresponding Write*
function for writing to an io.Writer
.
func (w http.ResponseWriter, r *http.Request) {
templates.WritePage(w)
}
The above example isn't all that exciting, let's expand on what we have and
create a more composable template. Template inheritance is possible via
{% interface %}
. With this we can define an interface for other pages to
implement, that can then be handed off to a function for rendering the final
template,
{%
interface Page {
Title()
Body()
}
%}
{% func RenderPage(p Page) %}
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>{%= p.Title() %}</title>
</head>
<body>{%= p.Body() %}</body>
</html>
{% endfunc %}
we could then define a BasePage
struct using {% code %}
that would provide
a base implementation of Page
that every other template could embed.
{% code
type BasePage struct {}
%}
{% func (p BasePage) Title() %}Personal blog{% endfunc %}
{% func (p BasePage) Body() %}{% endfunc %}
With our example, assume we're putting together some templates for a blog, we
would perhaps want templates to display the posts, and an individual post, let's
implement these to adhere to the Page
interface,
{% code
type Post struct {
BasePage // Provide BasePage so we implement the interface and can
// override the Title and Body.
Post *post.Post // Assume this is the data we're passing to the template.
}
%}
{% func (p Post) Title() %} p.Post.Title - {%= p.BasePage.Title() %}{% endfunc %}
{% func (p Post) Body() %}
<div class="post-body">{%s p.Post.Body %}</div>
{% endfunc %}
{% code
type Posts struct {
Posts []*post.Post
}
%}
{% func (p Posts) Body() %}
{% if len(p.Posts) == 0 %}
No posts found.
{% else %}
<ul>
{% for i, post := range p.Posts %}
<li id="post-{%d i %}"><a href="/{%s post.Slug %}">{%s post.Title %}</a></li>
{% endfor %}
</ul>
{% endif %}
{% endfunc %}
then in our Go code we would do something like the following to have them render with the loaded data.
func ShowPost(w http.ResponseWriter, r *http.Request) {
// Load in the post from the request.
var p *post.Post
tmpl := templates.Post{Post: p}
templates.WriteRenderPage(w, tmpl)
}
func ListPosts(w http.ResponseWriter, r *http.Request) {
// Load in the posts from the request.
posts := make([]*post.Post, 0)
tmpl := templates.Posts{Posts: posts}
templates.WriteRenderPage(w, tmpl)
}
The above example would give us a fairly bare bones HTML page, which is fine,
but if we want it to look nice we would want to load in some CSS, so let's do
that. Now, typically with web development it is encourage to load in CSS via
an external stylesheet. With the templates we have setup we could very easily
do this by modifying the RenderPage
function like so,
{% func RenderPage(p Page) %}
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>{%= p.Title() %}</title>
<link rel="stylesheet" type="text/css" href="/assets/css/style.css">
</head>
<body>{%= p.Body() %}</body>
</html>
{% endfunc %}
now this is fine. However, I'm of the radical opinion that it is perfectly fine
to have the CSS inline alongside the HTML, provided that the CSS is of a
reasonable size. This can be achieved via {% cat %}
,
{% func RenderPage(p Page) %}
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>{%= p.Title() %}</title>
</head>
<body>{%= p.Body() %}</body>
<style type="text/css">{% cat "../css/style.css" %}</style>
</html>
{% endfunc %}
cat
will emit the file contents as plaintext. The above example assumes that
css
is a directory that sits alongside templates
in the root of the project.
Of course inlining CSS and JS will increase the overall size of the HTML
document, but this can be remedied via {% stripspace %}
in quicktemplate. This
will strip any unnesessary space between tags and lines and produce a minified
HTML document, reducing the overall size of what we send over the wire.
{% stripspace %}
{% func RenderPage(p Page) %}
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>{%= p.Title() %}</title>
</head>
<body>{%= p.Body() %}</body>
<style type="text/css">{% cat "../css/style.css" %}</style>
</html>
{% endfunc %}
{% endstripspace %}
A quick tangent here about inlining CSS. This is something that I have started doing, since quicktemplate makes it pretty easy, and I think that this is something that should be done more, assuming of course that the CSS is of a reasonable size. This is what I meant by when I said all-in-one HTML pages, whereby additional content such as CSS and JS is simply placed alongside the HTML content. Why? Simply because I believe that when developing a web application you should strive to reduce the number of total requests made when initially loading up the web page (multi-media exempt from this). Not everyone uses fibre broadband, and some people may live deep in the countryside with spotty coverage. Because of this, web developers should aim to reduce the overall page sizes, and requests for their applications. These things add up over time when it comes to bandwidth costs. And, with a sufficiently fast enough page load, navigation between pages becomes indistinguishable from that of a single-page application. This is something I've done with Djinn CI, that you can see here for example, and another here. When you load up the page there are two requests only, one for the page itself, and another for the favicon. Granted, the landing page fails in this regard (4 requests total including favicon). But, the primary application pages itself achieve this, and all because of quicktemplate.
So, if you're looking to do some web development in Go, and are looking for a library for some templating that will allow you to generate fast templates, I implore you to try out quicktemplate. It has definitely allowed me to create more efficient HTML documents.