Go 1.11: WebAssembly for the gophers

Nicolas Lepage
Zenika
Published in
10 min readJun 20, 2018

--

In February 2017, the issue for WebAssembly support was opened at golang/go by Brad Fitzpatrick member of the Go team.

Four months later, Richard Musiol, author of GopherJS, enters the discussion with some “naive” ideas on how to add WebAssembly as a new target for Go compiler… finally posting a “fyi: I have started implementing this” in early November 2017.

Finally the issue was closed in June 2018, and the brand new support for the WebAssembly target merged in the golang/go repository’s master branch then released with Go 1.11 in August 2018.

This is the occasion to mix two of my favorite languages, let’s take a peek at what we can do with this first implementation!

All the examples shown in this article may be easily run using the image tagged examples from nlepage/golang_wasm:

Then visit http://localhost:32XXX/ and follow the links to the examples.

Warning! As of February 2019 and the release of Go 1.12, this article will be/is outdated:

  • js.Callback is renamed to js.Func, js.NewCallback() to js.FuncOf() (js.NezEventCallback() is deleted)
  • Callbacks are now executed synchronously, and support a return value

Hello Wasm!

Making a basic hello world has already been covered by some really good articles out there, so let’s just go over it quickly.

The first thing you need is an up to date Go toolkit (1.11 or higher). You may also use a docker image from the official Go repository.

Now you can write a traditional helloworld.go and compile it with the following command:

You may also use a Dockerfilelike this to compile:

The final step consists in using the wasm_exec.html and wasm_exec.js files, available in golang/go’s misc/wasm directory of the toolkit, to execute test.wasm in the browser (wasm_exec.jsexpects the binary to be named test.wasm, that’s why we are using this name).

You just have to serve the 3 files, using nginx for example, then wasm_exec.html will display a “Run” button (enabled only if test.wasm has been loaded correctly).

Be warned that test.wasm has to be served with the application/wasm MIME type, otherwise the browser may refuse to load it (nginx for instance needs an updated mime.types file).
You can use the image tagged nginx from nlepage/golang_wasm, which has the correct MIME type and already includes wasm_exec.html and wasm_exec.js in the /usr/share/nginx/html/ directory.

Now click the “Run” button, then open your browser’s console and you should see a console.log of your hello world (wasm_exec.js captures test.wasm's standard output and calls console.log for each new line).

The sources for a docker image with this hello world are available in the examples/hello directory of my github golang-wasm repository.

Calling JS from Go

Now that we have successfully executed our first WebAssembly binary compiled from Go, let’s explore a little further the capabilities of this first implementation of Go’s wasm.

A new package syscall/js has made its entry in Go’s standard library, the first file to look at in there is js.go.

A new type js.Value is available, which represents a JavaScript value.
It offers a simple API to manipulate and interact with any type of JavaScript value:

  • js.Value.Get() and js.Value.Set() retrieve and set properties on an Object value
  • js.Value.Index() and js.Value.SetIndex() retrieve and set values in an Array value
  • js.Value.Call() calls a method on an Object value
  • js.Value.Invoke() invokes a function value
  • js.Value.New() invokes the new operator on a reference representing a JS type
  • Some more methods to retrieve a JavaScript value in its corresponding Go type such as js.Value.Int() or js.Value.Bool()

And finally some interesting functions:

  • js.Undefined() gives the js.Value corresponding to JS’s undefined
  • js.Null() gives the js.Value corresponding to JS’s null
  • js.Global() gives the js.Value giving access to JS’s global scope
  • js.ValueOf() which accepts any Go basic type and returns the corresponding js.Value

Instead of sending the message to os.StdOut, let’s display it in an alert dialog using JS’s window.alert().

Since we are in a browser, the global scope is the window, hence the first thing we need to do is retrieve alert() from the global scope:

Now we have a variable alert typed js.Value which is a reference to JS’s window.alert, we may use js.Value.Invoke()on it:

As you can see, no need to call js.ValueOf() before handing the arguments to Invoke, it accepts a variadic of interface{}and runs the values through ValueOf for us.

Now our new program should look like this:

As for our first hello world, we just need to build to a file named test.wasm, and we can reuse wasm_exec.html and wasm_exec.js as is.

Now when we click the “Run” button, an alert dialog with our message should show up.

The sources for a docker image with this “Go to JS” call are available in the examples/js-call directory of my github golang-wasm repository.

Calling Go from JS

Calling JS from Go is pretty easy, now let’s explore some more syscall/js, a second file to look at is callback.go.

There is a new js.Callback type, which represents a Go func wrapped in order to be used as a JS callback.

There is also a new js.NewCallback() function which takes a func accepting a slice of js.Value (and returning nothing) and returns a js.Callback.

In there we also find some mechanic to manage the active callbacks, and a js.Callback.Release() which must be called when a callback won’t be used anymore in order to free up corresponding resources.

In addition there is a js.NewEventCallback() function to ease the reception of JS events.

Let’s try to do something simple: trigger a Go fmt.Println from the JS side.

The first thing we need to do is make some adjustments in wasm_exec.html, we have to be able to receive the callback from Go in order to call it.

First of all let’s add a text input, in order to be qble to custo;ize the messqge printed in the console:

For now the run() function that executes the wasm binary looks like this:

It starts the wasm binary and waits for it to terminate, then it re-instantiates it for the next run.

Let’s adapt it in order to receive the Go callback before calling it:

Before executing the binary, we create a Promise for the callback reception, then after starting the binary, we wait for the Promise’s resolution before calling the callback.

Using the var keyword in the <script> element makes the setPrintMessage reference available in the global scope hence for the wasm binary.

And this is it on the JS part!

Now on the Go part, we need to create the callback, send it to the JS side and wait for it be called.

The first thing we need is a channel to notify that our callback has been called:

Then we have to write the actual printMessage() func:

As you can see the arguments are received in a slice of js.Value, so we have to call js.Value.String() on the first element of the slice in order to retrieve the message in a Go string.

Now we can wrap this func in a callback:

Then invoke the JS setPrintMessage() function, exactly like when we called window.alert():

The last thing we need to do is wait for callback to be called:

This last part is important because the callbacks are executed in a dedicated goroutine, hence the main goroutine has to wait for the callbacks to be called, otherwise the wasm binary will terminate prematurely.

The resulting Go program should look like this:

As for our previous examples, we just need to build to a file named test.wasm, this time we have to replace wasm_exec.htmlby our version, and we can reuse wasm_exec.js as is.

Now when we click the “Run” button, as for our first example the message is printed in the browser’s console, but this time it is possible to customize it with the HTML input.

The sources for a docker image with this “JS to Go” call are available in the examples/go-call directory of my github golang-wasm repository.

Long running

Calling Go from JS is a little more cumbersome than calling JS from Go, especially on the JS part.
This is partly because callbacks are executed asynchronously.

Let’s try something different: Why not have a wasm binary which will not terminate just after the callback is called, but will go on running and receive other calls?

This time let’s start on the Go side, like in our previous example we need to create a callback and send it to the JS side.
We are going to add a calls counter in order to keep track of how many times our callback has been called.

Our new printMessage() func is going to print the received message and the value of our calls counter:

Creating the callback and sending it to the JS side is exactly the same as in our previous example:

But this time we do not have the done channel to notify us when to terminate the main goroutine. One way could be to indefinitely block the main goroutine with and empty select:

This is not very satisfying, our wasm binary will never shutdown cleanly and will likely be killed when wasm_exec.html is unloaded by the browser.

What we could do is listen for the beforeunload event of the page, we will need a second callback to receive the event and notify the main goroutine via a channel:

This time so our new beforeUnload() func will take only one js.Value argument which is the event:

Then we can wrap it in a callback using js.NewEventCallback(), and register it to the JS side:

Finally we have to replace the empty select by a receive on the beforeUnloadCh channel:

Our final program should look like this:

Now on the JS part, the loading of the wasm binary previously looked like this:

Let’s adapt it in order to start the binary straight after its loading:

And we’ll replace our “Run” button by a button to trigger printMessage():

Finally this time we need a setPrintMessage() in order to store the callback which will be used several times:

Now when we click on the “Print message” button, we should see the message of our choice and the calls counter printed in the browser’s console.

Then if you check the “Preserve log” option of your browser’s console and trigger a refresh of the page, you should see the “Bye Wasm!” message in the console.

The sources for a docker image with this are available in the examples/long-running directory of my github golang-wasm repository.

More to explore…

As you can see, the syscall/js API we have explored goes straight to the point, and manages our simple examples with rather few code.
An easier interoperability between JS and Go functions would be welcome, if you would like to see my try at simplifying the Go -> JS interoperability have a look at:

For now the Go callbacks have no return values, so it’s not possible to return a value to JS directly from a Go function.
One warning about the callbacks: these are all executed in the same goroutine, so if you are making some blocking operations in a callback don’t forget to create a new goroutine of your own, otherwise you will block any other callback execution.

All the core features of the language are already available, including concurrency. For now all our goroutines will run in a single thread, but this might change in the future.

In our examples we only used the fmt package from the standard library, but a much larger part is available.
I have not dug into the file system support, but it seems that it is supported through Node.JS.

Finally what about performance? It would be interesting to run some benchmarks in order to see how Go wasm compares to equivalent pure JS code.

Thank you for reading, have fun!

Published: 20 June 2018
Last updated: 15 January 2019

--

--