published on

js/template

One of my favorite packages in the Go standard library is html/template. Not only does it provide a solid templating language equivalent to text/template, but it ensures its safety of any HTML or JavaScript you throw at it. Unfortunately, it can be a little limiting when working with external JavaScript resources, but there are a couple options for working around those limitations.

Most websites like to separate the concerns of markup and scripting by having separate .html and .js files for their project. However, html/template always assumes that it’s working with .html files. JavaScript escaping is enabled whenever the template engine encounters a <script> tag, but you can’t pass it a pure JavaScript file without running into a “malformed HTML” error. Fortunately, it’s not too difficult to trick the engine into properly parsing JavaScript sources.

There are two ways to add JavaScript code to an HTML page: embedded directly, or referenced via an src attribute. The former may be useful if your scripts are very small, but in a general sense, the latter is usually the approach that you want, so that’s the one that I’ll show here.

The trick to getting html/template to properly parse your JavaScript code is simply to wrap its contents in a <script> tag, then trim it off before sending it back to the client:

package main

import (
	"bytes"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
)

// ScriptFetcher is an HTTP handler that behaves similarly to a
// static file handler, except that it only serves JavaScript
// files, which it templates before sending to the client.
//
// In this example, the handler looks for a local JavaScript
// file by trimming off the leading slash, e.g.
// "GET /script/index.js" will look for "script/index.js".
func ScriptFetcher(w http.ResponseWriter, r *http.Request) {
    contents, err := ioutil.ReadFile(r.URL.Path[1:])
    if err != nil {
        // Kind of a sledgehammer as far as error-handling goes,
        // but it's good enough for the purposes of this example.
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Create a new template by wrapping the JavaScript contents
    // in an HTML <script> tag.
    t, err := template.New("").Parse(
        fmt.Sprintf("<script>%s</script>", string(contents)),
    )
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Execute the script against an in-memory buffer,
    // since we need to trim the </script> tag off the end of the
    // result, which requires knowing the total length of the
    // data.
    //
    // In a real-world situation, you'd probably want to
    // actually pass in some data value other than `nil`.
    var buf bytes.Buffer
    if err := t.Execute(&buf, nil); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Now the buffer contains our script result surrounded by the
    // <script> tag, so we need to trim that off before sending it
    // to the client.
    var (
        result = buf.Bytes()
        start  = len([]byte("<script>"))
        end    = len(result) - len([]byte("</script>"))
    )
    if _, err := w.Write(result[start:end]); err != nil {
    	// Poor man's logging.
    	fmt.Printf("error writing response: %s\n", err.Error())
    }
}