This is a rapid post about webassembly. Its goal is to act as a reminder for me more than a tutorial on how to use it.

The upcoming version of go 1.11 will have support for Wasm. @neelance has done most of the work of implementation. The support for wasm can already be tested by extracting his working branch from GitHub.

See this article for more information.

Setup of the toolchain

To generate a wasm file from a go source, you need to get and patch the go toolset from the sources:

~ mkdir ~/gowasm
~ git clone https://go.googlesource.com/go ~/gowasm
~ cd ~/gowasm
~ git remote add neelance https://github.com/neelance/go
~ git fetch --all
~ git checkout wasm-wip
~ cd src
~ ./make.bash

Then to use this version of go, point the GOROOT to ~/gowasm and use the binaries present in ~/gowasm/bin/go.

First sample

As usual, the first sample is a “hello world”. Let’s write this:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
        fmt.Println("Hello World!")
}

and compile it into a file called example.wasm:

GOROOT=~/gowasm GOARCH=wasm GOOS=js ~/gowasm/bin/go build -o example.wasm main.go

Running the sample

Here is an extract from The official documentation:

While there are future plans to allow WebAssembly modules to be loaded just like ES6 modules (…), WebAssembly must currently be loaded and compiled by JavaScript. For basic loading, there are three steps:

  • Get the .wasm bytes into a typed array or ArrayBuffer
  • Compile the bytes into a WebAssembly.Module
  • Instantiate the WebAssembly.Module with imports to get the callable exports

Luckily, the Go authors made this task easy by providing a Javascript Loader. This loader is here ~/gowasm/misc/wasm/wasm_exec.js. It comes with an HTML file that takes care of gluing everything in the browser.

To actually run our file, let’s copy the following files in a directory and serve them by a webserver:

~ mkdir ~/wasmtest
~ cp ~/gowasm/misc/wasm/wasm_exec.js ~/wasmtest
~ cp ~/gowasm/misc/wasm/wasm_exec.html ~/wasmtest/index.html
~ cp example.wasm ~/wasmtest

Then edit the file index.html to run the correct sample:

1
2
3
4
5
6
7
// ...
WebAssembly.instantiateStreaming(fetch("example.wasm"), go.importObject).then((result) => {
        mod = result.module;
        inst = result.instance;
        document.getElementById("runButton").disabled = false;
});
// ...

In theory, any web server could do the job, but I had faced an issue when I tried to run it with caddy. The javascript loader is expecting the server to send the correct mime type for the wasm file.

Here is a quick hack to run our test: to write a go server with a particular handler for our wasm file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
        "log"
        "net/http"
)

func wasmHandler(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/wasm")
        http.ServeFile(w, r, "example.wasm")
}
func main() {
        mux := http.NewServeMux()
        mux.Handle("/", http.FileServer(http.Dir(".")))
        mux.HandleFunc("/example.wasm", wasmHandler)
        log.Fatal(http.ListenAndServe(":3000", mux))
}

Note Setting up a special router to handle all the wasm files is no big deal, but as I said, this is a POC and this post are side notes about it.

Then run the server with go run server.go and point your browser to http://localhost:3000.

Open the console, and voilĂ  !

Interacting with the browser.

Let’s interact with the world.

Addressing the DOM

The syscall/js package contains the functions that allow interaction with the DOM through the javascript API.

To get the documentation about this package, just run:

GOROOT=~/gowasm godoc -http=:6060

and point your browser to http://localhost:6060/pkg/syscall/js/.

Let’s write a simple HTML file that displays an input field. Then, from the webassembly, let’s place an event on this element and trigger an action when this event fires.

Edit the index.html and place this code just below the run button:

1
2
3
        <button onClick="run();" id="runButton" disabled>Run</button>
        <input type="number" id="myText" value="" />
</body>

Then modify the Go file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import "fmt"

func main() {
          c := make(chan struct{}, 0)
         cb = js.NewCallback(func(args []js.Value) {
                  move := js.Global.Get("document").Call("getElementById", "myText").Get("value").Int()
                  fmt.Println(move)
          })
          js.Global.Get("document").Call("getElementById", "myText").Call("addEventListener", "input", cb)
          // The goal of the channel is to wait indefinitly
          // Otherwise, the main function ends and the wasm modules stops
          <-c
}

Compile the file as you did before and refresh your browser… Open the console and type a number in the input field…. voilĂ 

Exposing a function

This one is a bit trickier… I did not find any easy way to expose a Go function into the Javascript ecosystem. What we need to do is to create a Callback Object in the Go file and assign it to a Javascript Object.

To get a result back, we cannot return a value to the callback and we are using a javascript object instead.

Here is the new Go code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main
import (
        "syscall/js"
)

func main() {
        c := make(chan struct{}, 0)
        add := func(i []js.Value) {
                js.Global.Set("output", js.ValueOf(i[0].Int()+i[1].Int()))
        }
        js.Global.Set("add", js.NewCallback(add))
        <-c
}

Now compile and run the code. Open back your browser and open the console.

If you type output it should return a Object not found. Now type add(2,3) and type output… You should get 5.

This is not the most elegant way to interact, but it is working as expected.

Conclusion

The wasm support in Go is just starting but is in massive development. Many things that are working by now. I am even able to run a complete recurrent neural network coded thanks to Gorgonia directly in the browser. I will explain all of this later.