Go Templates

I find Go templates to be very confusing. Maybe I am dumb, but I constantly find it difficult to get my html/templates working properly in a web application. There are a ton of stupid tutorials on the Internet, but they all fail to cover some basic cases:

  • How do you build template objects at code-start, not per-request? Most examples load templates (ParseFiles()) while handling a request. Good for dev debugging, but there must be another way.
  • How do you structure your templates for the cleanest reuse? For example you have a static page layout and for different urls you want to just change the main content.

Structured templates

To explain the second one a bit more, when you have a template language that has an "include" function for loading more templates you could use this pattern:

In header.tmpl:

<head>
  <title>Boo!</title>
</head>
<body>
  <div id="main">

In footer.tmpl:

  </div>
</body>

In page1.tmpl:

[ include "header.tmpl" ]
    <h1>Page 1 main content</h1>
[ include "footer.tmpl" ]

In page2.tmpl:

[ include "header.tmpl" ]
    <h1>Page 2 main content</h1>
[ include "footer.tmpl" ]

You get to re-use the header and footer templates, but see how the <div id="main"> is split between the header and footer? If you change the header to increase the depth of the main content you must remember to also change the footer. A much better pattern would be:

In layout.tmpl:

<head>
  <title>Boo!</title>
</head>
<body>
  <div id="main">
    [ include "content.tmpl" ]
  </div>
</body>

However you want content.tmpl to have different content depending on the url... Perhaps you can make this a variable, if supported by the templating language:

<head>
  <title>Boo!</title>
</head>
<body>
  <div id="main">
    [ include ${content} ]
  </div>
</body>

Then in your routes you set $content depending on what page you are on. But if your template language doesn't support this kind of thing then you are forced to render your $content into a variable, and then pass that variable into the layout.tmpl template as a value: YUCK!!

Back to Go templates

Let's see if we can just get a really basic template running with a test.html file:

<head>
  <title>Hello!</title>
</head>
<body>
  <h1>Hello!</h1>
</body>

And a main.go:

package main
import (
    "os"
    "html/template"
)
func main() {
    t := template.Must(template.New("name").ParseFiles("test.html"))
    err = t.Execute(os.Stdout, nil)
    if err != nil {
        panic(err)
    }
}

And when you run it:

panic: template: "name" is an incomplete or empty template

goroutine 1 [running]:
main.main()
        /go-templates/main.go:15 +0xee
        exit status 2

Wait, what?? My code looks like every other tutorial example out there... Let's drop the template.New("name") part:

package main
import (
    "os"
    "html/template"
)
func main() {
    t := template.Must(template.ParseFiles("test.html"))
    err := t.Execute(os.Stdout, nil)
    if err != nil {
        panic(err)
    }
}

Ok that worked. So what's the difference between these two:

template.New("name").ParseFiles("test.html")

template.ParseFiles("test.html")

Understanding

The documentation for html/template seems to skip a lot of information, until you realise that html/template has the same API as text/template. Sure enough, text/template documentation covers all the basics. Somewhere in that documentation (fairly far down the page) we see this:

Associated templates

Each template is named by a string specified when it is created. Also, each
template is associated with zero or more other templates that it may invoke
by name; such associations are transitive and form a name space of
templates.

A template may use a template invocation to instantiate another associated
template; see the explanation of the "template" action above. The name must
be that of a template associated with the template that contains the
invocation.

Which reads like it was written by a robot, but let's try to unpack it a bit:

  • Internally the template object stores templates associated with a name
  • There can be multiple names in a single Template object
  • You can declare these names within templates using {{define "name"}} ... {{end}}, and "call" them within templates {{template "name"}}

A little bit down we see this great example:

{{define "T1"}}ONE{{end}}
{{define "T2"}}TWO{{end}}
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
{{template "T3"}}

So internally in the template object we can see names being defined (T1,T2..) and then rendered by name. Let's play with this a bit by putting this into a file define.tmpl, and trying a few things:

t := template.Must(template.ParseFiles("define.tmpl"))
t.Execute(os.Stdout, nil)

Prints ONE TWO as expected. Now let's do something a bit weirder using ExecuteTemplate:

t := template.Must(template.ParseFiles("define.tmpl"))
err := t.ExecuteTemplate(os.Stdout, "T1", nil)

Prints ONE only and ignores the rest of the templates! So we start to understand a little bit more about what is going on internally. What if we try to go back to our original example and try to get it to work:

t := template.Must(template.New("name").ParseFiles("test.html"))
err = t.ExecuteTemplate(os.Stdout, "test.html", nil)

So this works... But still, what is the purpose of using template.New("name") at all? The answer can be seen with this example template.tmpl:

{{define "banana"}}
BANANA!
{{end}}
{{define "hurrah"}}
HURRAH!
{{end}}
Hmmm, hello...

And this main:

func main() {
    t1 := template.Must(template.New("banana").ParseFiles("template.tmpl"))
    t1.Execute(os.Stdout, nil)

    t2 := template.Must(template.New("hurrah").ParseFiles("template.tmpl"))
    t2.Execute(os.Stdout, nil)

    t3 := template.Must(template.New("banana").ParseFiles("template.tmpl"))
    t3.ExecuteTemplate(os.Stdout, "hurrah", nil)

    t4 := template.Must(template.New("banana").ParseFiles("template.tmpl"))
    t4.ExecuteTemplate(os.Stdout, "template.tmpl", nil)
}

Prints this (extra newlines removed):

BANANA!
HURRAH!
HURRAH!
Hmmm, hello...

Do some guru-meditation on that and you see:

  • Using template.New("name") means "name" will be the default executed template when you use Execute()
  • You can override this by using ExecuteTemplate() and passing a name
  • The ParseFiles("template.html") creates an internal name template.html representing the parts outside a define

If you visualise the internal state of the t1 template object it might look something like this:

t1 == {
    "default_template": "banana",
    "chunks": {
        "banana": "BANANA!",
        "hurrah": "HURRAH!",
        "template.tmpl": "Hmmm, hello...",
    }
}

Probably an incorrect model, but more or less. Regardless, passing a different name to New() just changes the default_template. Assuming all that is true we should be able to break a template object by having multiple definitions of the same thing:

file1.tmpl:

{{define "banana"}}
file1 banana
{{end}}

file2.tmpl:

{{define "banana"}}
file2 banana
{{end}}

With:

t1 := template.Must(template.New("banana").ParseFiles("file1.tmpl, "file2.tmpl"))
t1.Execute(os.Stdout, nil)

Prints:

file2 banana

Either my internal mental model is wrong, or file2.tmpl just silently overrides the definition of "banana". This can be confirmed by modifying file1.tmpl slightly:

{{define "banana"}}
file1 banana
{{end}}
{{template "banana"}}

And changing the code:

t1 := template.Must(template.New("banana").ParseFiles("file1.tmpl, "file2.tmpl"))
t1.ExecuteTemplate(os.Stdout, "file1.tmpl", nil)

Prints:

file2 banana

Can you see why? Guru-meditation if not.

Back to structured templates

Phew! With a much better understanding of how the template system works, is it possible to get to the ideal:

<head>
  <title>Boo!</title>
</head>
<body>
  <div id="main">
    [ different content template per page ]
  </div>
</body>

Actually, none of what we discovered helps here. It does not seem possible to do this:

{{template $name}}

Where name is a variable passed in. If we try something else:

In page1.tmpl:

{{define "content"}}
Page 1 content
{{end}}

In page2.tmpl:

{{define "content"}}
Page 2 content
{{end}}

In layout.tmpl:

<body>
{{template "content"}}
</body>

If we ParseFiles("page1.tmpl", "page2.tmpl", "layout.tmpl") then the page2 content will override page1 :-/. There is only one thing for it unfortunately: have multiple template objects:

templates := make(map[string]*template.Template)
templates["page1"] = template.ParseFiles("page1.tmpl", "layout.tmpl");
templates["page2"] = template.ParseFiles("page2.tmpl", "layout.tmpl");

Then, later depending on the page:

templates["page1"].ExecuteTemplate(os.Stdout, "layout.tmpl", nil)

Conclusion

Most of the bullshit you read in the Internet about Go templates just completely gloss over these details. I suspect the authors of these "tutorial" pages don't actually understand what is happening and are just after the clicks. The Go documentation for html/template hints that you should read text/template but it should probably be more specific about that. It is also not that great at explaining what is going on internally.