This post walks through how I created the [Go Templates Playground][playground]. It pulls Go template rendering capabilities directly into your browser with WebAssembly (Wasm). I was motivated to this after recently seeing the utility of other similar environments out there for different programming languages1. The Go template language has relatively wide adoption in projects like Helm and Hugo, which this tool can be used to prototype and experiment for. I’ve also been interested in incorporating Wasm into a deployed project, and this was a great opportunity for it, as well as chance to play with alternate approaches to help bring what are traditionally backend languages to the browser. It is built into my personal site to simplify development and deployment.
If you just want to jump over to the playground, check it out here.
Turning Go into Wasm
As a first step, we setup a Go module for the functionality we want to expose through Wasm. I did this as a separate Go module within the same repository as the site itself, under exp/go-templates. Within that file, we utilize the standard library (but experimental) syscall/js package to expose basic functionality that will compile a template and render it with the input data.
As the Wasm support provided by Go is somewhat experimental and does not seem not all that well documented, I found this webpage golangbot.com/webassembly-using-go/ to be a helpful guide, walking through the fundamental steps needed to get things working.
In the below snippet we define a variable called
render which is a function
wrapped with the
syscall/js.FuncOf function. In the last
line of the snippet, that variable is then set on the
global object, making it
available globally on the page that this code is loaded into. You can click
through at the bottom of the snippet to see the full file.
The function itself is relatively straightforward, performing the following steps:
- Parse the template
- Decode input data
- Execute the template
For executing the template, we also optionally pull in the sprig template function library, which is commonly used in projects that make use of Go template to add a large suite of functions for common tasks. It is optional to provide a toggle for whether or not it should be disabled/enabled.
Error handling is a bit “fast and loose” here. I opted to keep things simple by returning a single string that will be rendered into the output in order to display error messages directly to the user. That could be split out for a more customized UI by returning a structured object for the result.
With that code in place, we need to build it into the
.wasm file that will be
executed within the browser environment. There are two pieces to that puzzle,
captured in these Makefile rules. First, the Wasm produced by Go is not be
directly interpretable by the browser. There is an intermediate layer in a file
wasm_exec.js which provides several utilities for initialization. We’ll
see how that’s used in a moment, but for now we are just copying it into our
TARGET directory for inclusion with the site.
The next section here is for compiling the Wasm itself. That is achieved with
go build command, merely by adding in
GOOS=js GOARCH=wasm as
environment variables. The
$@ Makefile syntax reverences the name of the
current rule, producing a file called
go-templates.wasm for us. The latter
portion of that rule which specifies
$(wildcard *.go **/*.go) indicates that
this rule should re-run whenever those files change.
Bringing Wasm into the UI
Now that we have our
.wasm files in hand, we need to pull
them into our webpage. We’ll do this for both files using Hugo
templating logic, upon which this site is built. In the below snippet, we can
actually pull the reference to the Wasm file into a Go template variable within
a comment (so that JS syntax highlighting/linting is undisturbed) using Hugo
Pipes, and then pass that link into our Preact component
for use at initialization time. By using Hugo Pipes, we can add a hash to the
filename, allowing for longer duration caching in the browser, which is
particularly useful here since these the Wasm files are somewhat sizable
We then pull in Go’s
wasm_exec.js wrapper in similar fashion, using Hugo
Pipes, but in this case as it’s a standard
.js file we can put it directly
within a script tag, SRI hash and all.
There are two pieces of the WebAssembly puzzle here that are worth taking note
of. First is the call to
new Go(), which activates the logic we brought in
wasm_exec.js earlier. Second is the call to
WebAssembly.instantiateStreaming, which takes in a
stream from fetching the Wasm file that we got earlier via Hugo Pipes, and
instantiating it, as it is streaming in from the server. This is generally the
most efficient way to pull in Wasm code. We pass in
go.importObject as the
importObject parameter to this function, which maps the Wasm assembly into our
running application in a useful way.
Once this is complete, we either mark it as successful in the promise, which causes the application to perform it’s first template rendering, or we mark the error accordingly and display it to the user. The error display is useful here in particular because Wasm is not well-supported on older browser environments.
The rest of the app is a relatively straightforward Preact (React-style) application. It is somewhat monolithic as it is a relatively simple use case, but gets the job done. You can see the file in it’s entirety here:
One interesting piece of that code is where the Wasm code is invoked. The
newState method shown here is a helper to produce the next version of the
Preact “state” that is used to render the application. This method is called at
various locations to add the newly rendered template state, using the
ExpRenderGoTemplate global function exposed from the Go Wasm, to the overall
To tie things off, I added a basic end to end test to ensure that the basic functionality keeps working moving forward. These tests are written in puppeteer and because I already had the puppeteer logic up and running for other areas of the website, it was as simple as adding the test case we see here.
Odds and ends
If you are interested in GopherJS, one opportunity to be aware of is that you can pair GopherJS itself with libraries like gopherjs/jquery that control the UI rendering layer directly, allowing you to put as much of your rendering logic into the Go code as you might like. I experimented with these options but ultimately decided that it would be more efficient and seem to have a better end result to build the rendering layer directly with web-oriented technologies, which is how I landed on the Preact-based approach.
One thorn in the side of this project was that I did need to modify my site’s
Content-Security-Policy to get the WebAssembly to load in all browsers. I use
Firefox as my daily driver, where this all works just fine, but in order for it
to work in all of Safari, Chrome, and Edge I needed to add
unsafe-eval to the
script-src directive, allowing the WebAssembly to be brought in directly. I
soon hope to swap this out for
wasm-unsafe-eval which is a recent
replacement3. Going beyond that however, Wasm needs a mechanism to be
loaded in that is not termed “unsafe”4.
Another note for someone wading into these waters is that your editor by default
may not have its tooling setup to work with the
GOOS=js GOARCH=wasm targets,
and is likely to have some errors in trying to perform normal operations on
syscall/js. To get around that, you can hopefully do some
sort of directory-specific customization, which is what I did here for my setup
of Vim with
If you made it this far I hope that you enjoyed this post, and that you will get some utility out of the templating playground itself! If you have any feedback or questions, feel free to leave a comment or reach out. If you want to experiment with the code locally, check out the README for the repository to install, and you can play around with the files above. Contributions or suggestions would be very welcome!
One inspiration for this project was http://jinja.quantprogramming.com/ which provides similar functionality for the jinja templating language. I used their overall visual UI layout as a starting point for this project. ↩︎
There is a rather fun tweet from one of the creators of Docker: “If Wasm+WASI existed in 2008, we wouldn’t have needed to created Docker”: https://twitter.com/solomonstre/status/1111004913222324225 ↩︎
This GitHub repository seems to be the most recent proposal along these lines: https://github.com/WebAssembly/content-security-policy/blob/main/proposals/CSP.md with discussion happening in the issues: https://github.com/WebAssembly/content-security-policy/issues?q=is%3Aissue+sort%3Aupdated-desc ↩︎