[draɪʃtat]

let’s do this!

Golang, vuejs and custom delimiters

I ran into a problem today that took me some time to figure out. Hopefully I can save you some time, dear reader. If you are struggling with setting custom delimiters on a golang template, this is for you.

The situation

So, consider this template, used by your golang application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<h1>{{ .Title }}</h1>

<div id="app">
{{ message }}
</div>

<script type="text/javascript">
    let app = new Vue({
        el: '#app',
        data: {
            message: 'Hello Vue!'
        }
    })
</script>

If you use it, it will crash, because vuejs and golang both are using {{ }} as delimiters, so you’ll get “undefined: message” or something like that.

Finding a solution

So, how to solve this? According to the interwebs, it’s easy.

Let’s say this is your handler that throws the error:

1
2
3
4
func indexHandler(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("templates/index.html")
	t.Execute(w, struct{Title: "Foo"})
}

You just need to restructure it a bit, using a call to template.Delims():
(error handling omitted for brevity)

1
2
3
4
5
func indexHandler(w http.ResponseWriter, r *http.Request) {
	tpl := template.New("index").Delims("[[", "]]")
	tpl, _ := tpl.ParseFiles("templates/index.html") 
	tpl.Execute(w, struct{Title: "Foo"})
}

So now we’re using template.New("index").Delims("[[", "]]") and this seems to be correct, but all you get is a blank page. But… why?

This is because of how go references templates internally. You are creating a new template on line 2, giving it the name index. On line 3 however, it’s overwritten and the name changes. Calling tpl.Execute() now does nothing, because it doesn’t have content to operate on, to put it simply.

Well, no prob chap, let’s name all the things!

1
2
3
4
5
func indexHandler(w http.ResponseWriter, r *http.Request) {
    tpl := template.New("index").Delims("[[", "]]")
	tpl, _ := tpl.ParseFiles("templates/index.html") 
	tpl.ExecuteTemplate(w, "index", struct{Title: "Foo"})
}

You may have guessed it, still nothing. So obviously the references are still off. Quick, some debug code to the rescue:

1
2
3
4
5
6
func indexHandler(w http.ResponseWriter, r *http.Request) {
    tpl := template.New("index").Delims("[[", "]]")
	tpl, _ := tpl.ParseFiles("templates/index.html")
	fmt.Println(tpl.DefinedTemplates()) 
	tpl.ExecuteTemplate(w, "index", struct{Title: "Foo"})
}

On the console, you’ll see:

1
2
; defined templates are: "index.html"
; defined templates are: "index.html"

The solution

Now things are becoming a bit more clear. When calling template.ParseFiles() go overwrites the template name with the file name of our parsed file.

The now working code is:

1
2
3
4
5
func indexHandler(w http.ResponseWriter, r *http.Request) {
    tpl := template.New("whatever").Delims("[[", "]]")
	tpl, _ := tpl.ParseFiles("templates/index.html")
	tpl.ExecuteTemplate(w, "index.html", struct{Title: "Foo"})
}

The HTML now has to to be changed to this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<h1>[[ .Title ]]</h1>

<div id="app">
{{ message }}
</div>

<script type="text/javascript">
    let app = new Vue({
        el: '#app',
        data: {
            message: 'Hello Vue!'
        }
    })
</script>

And voilá, you can vuejs in your go templates.

tl;dr

If you are in a situation where go’s http server is showing a blank page instead of your rendered template, chances are you got the naming wrong. Use template.DefinedTemplates() for debugging.

Comments powered by Talkyard.