How to use GopherJS to turn Go code into a JavaScript library
Introduction
My company is building apps that have a Go-based backend, and multiple frontend clients written in JavaScript.
We have some complex code that must run on both the clients and the servers independently, and which must also be guaranteed to deliver the same results from the same inputs.
We could create two different implementations and a test suite that compares them, but that requires writing tricky code twice, and maintaining them both whenever anything changes.
GopherJS seemed like a viable solution for writing code once, running it directly in Go, and also having the same code available within JS.
The problem is that GopherJS is explicitly intended to allow entire front-end applications to be written in Go, and is very much documented with this goal in mind. There are plenty of examples of how to call into JS libraries from GopherJS code — but basically nothing that shows how to create a JS module and call into a Go subroutine from JS.
So I’m going to tell how I did it, in hopes of saving others a bit of pain.
Structure
We’re going to need to write some wrapper functions. They’re written in Go, but they’re going to conform to JavaScript’s expectations. These functions can do whatever we want them to do, including using our own Go-based libraries, or most of the Go standard library.
Our code can easily accept and return standard JS datatypes. We do need to understand how those types are going to be interpreted. That is documented in the GopherJS godoc pages here. The part we are interested in right now are the second and third columns, labelled JavaScript type
and Conversions back to interface{}
.
Note that while strings come in as strings, all JavaScript Number
types come into Go as a float64
(there are no ints), and js Object
types are passed in as map[string]interface{}
. (We’ll talk about returning objects a bit later).
A Simple Function
Let’s say, for example, we want to use Go’s implementation of SHA-256 to create a hash from a string. From JS, a function definition for this would be defined as something like function hashit(s : string) : Uint8Array { … }
. We can write a wrapper function for it in Go like this:
To actually use GopherJS to make this available as a library, we also need to tell GopherJS how to export it. We do that by injecting the hashit function into the module exports with js.Module.Get(“exports”).Set(“hashit”, hashit)
.
Our module is going to build according to the conventions of the Go language — in particular, the source file name is unimportant, but the directory name containing the source file will be the name of the module we build. In this case, we have made a directory called `simple`.
Note that we’ve included a go generate
line so that we can build this using go generate (although it’s simple enough to just run the command manually).
Now we can do gopherjs build
to create a JS module, or go generate
to create a minified module. These will both create the module simple.js
, as well as simple.js.map
(for debugging).
To test it, we start node’s command line:
We can also create `test.js` to do this instead:
And then run it with `node test.js`:
$ node test.js
32
Passing Objects to Go Code
Suppose we wanted our hash function to hash the data taken from several fields of an object passed in. By default, the object will be treated as map[string]interface{}
. We create a new directory called objtest
and in it create objtest.go
:
Here’s the test script:
We build it, then test it in node with gopherjs build && node test.js
:
$ node test.js
Uint8Array [ 28, 48, 69, 120 ]
Sending Objects from Go code back to JS
It is possible to treat JS objects more like Go objects by creating a wrapper type. This also makes it pretty easy to manipulate fields in an object.
To do this, we use js.Object, which lets us create a wrapper for a JavaScript Object. A wrapper type literally generates a set of accessors for the object, and it must have its first field be of type *js.Object
(which is how it knows which JS object to manipulate). The first field can even be anonymous.
Note that fields that should be accessible from JS require a “js” tag. That looks like this:
Here’s how to use it from the JS side:
https://gist.github.com/735e56d52ef827efb3612d5b6ca48b19
Running it:
$ gopherjs build && node test.js
Uint8Array [ 28, 48, 69, 120 ]
The hash result is the same as the one where we used map[string]interface{}
.
Promises and Errors
One problem with the above is that there’s no error return; if the object doesn’t have the right fields, or the fields contain data of the wrong type, we’ve just ignored it here.
I honestly don’t know or care if there’s a way in Gopherjs to cause Go code to throw
a normal JavaScript exception. I’m not a big fan of those and much prefer to wrap things in Promises and handle errors as rejected Promises.
Consequently, the real goal of my effort was to do this with a Promise that could be rejected in case of an error.
There’s a useful Go library, github.com/miratronix/jopher
, which makes this fairly easy to do. The Promisify function accepts an idiomatic Go function and wraps its result in a Promise; if the last return value (following the Go convention) is of error
type, it will automatically reject the Promise appropriately.
We are going to construct a new JS object and set its fields, including a member function that returns a Promise. Because of the way GopherJS creates field accessors, you may not use object initializers to set these fields; you must assign them explicitly.
Here’s how it’s done:
It’s not really that hard to make a JavaScript library in Go using GopherJS, but it’s not exactly obvious. I hope the examples above will save some time and frustration.