Using WebAssembly

You can call a JavaScript function from Go and call a Go function from WebAssembly:

  1. package main
  2. // This calls a JS function from Go.
  3. func main() {
  4. println("adding two numbers:", add(2, 3)) // expecting 5
  5. }
  6. // This function is imported from JavaScript, as it doesn't define a body.
  7. // You should define a function named 'main.add' in the WebAssembly 'env'
  8. // module from JavaScript.
  9. func add(x, y int) int
  10. // This function is exported to JavaScript, so can be called using
  11. // exports.multiply() in JavaScript.
  12. //go:export multiply
  13. func multiply(x, y int) int {
  14. return x * y;
  15. }

Related JavaScript would look something like this:

  1. // Providing the environment object, used in WebAssembly.instantiateStreaming.
  2. env: {
  3. 'main.add': function(x, y) {
  4. return x + y
  5. }
  6. // ... other functions
  7. }
  8. // Calling the multiply function:
  9. console.log('multiplied two numbers:', wasm.exports.multiply(5, 3));

You can also simply execute code in func main(), like in the standard libraryimplementation of WebAssembly.

Building

If you have tinygo installed, it’s as simple as providing the correct target:

  1. tinygo build -o wasm.wasm -target wasm ./main.go

If you’re using the docker image, you need to mount your workspace into the image.Note the —no-debug flag, which reduces the size of the final binary by removingdebug symbols from the output. Also note that you must change the path to your Wasm file from /go/src/github.com/myuser/myrepo/wasm-main.go to whatever the actual path to your file is:

  1. docker run -v $GOPATH:/go -e "GOPATH=/go" tinygo/tinygo:0.8.0 tinygo build -o /go/src/github.com/myuser/myrepo/wasm.wasm -target wasm --no-debug /go/src/github.com/myuser/myrepo/wasm-main.go

Make sure you copy wasm_exec.js to your runtime environment:

  1. docker run -v $GOPATH:/go -e "GOPATH=/go" tinygo/tinygo:0.8.0 /bin/bash -c "cp /usr/local/tinygo/targets/wasm_exec.js /go/src/github.com/myuser/myrepo/

More complete examples are provided in the wasm examples.

How it works

Execution of the contents require a few JS helper functions which are calledfrom WebAssembly. We have defined these in tinygo/targets/wasm_exec.js. It isbased on $GOROOT/misc/wasm/wasm_exec.js from the standard library, but isslightly different. Ensure you are using the same version of wasm_exec.js asthe version of tinygo you are using to compile.

The general steps required to run the WebAssembly file in the browser includesloading it into JavaScript with WebAssembly.instantiateStreaming, orWebAssembly.instantiate in some browsers:

  1. const go = new Go(); // Defined in wasm_exec.js
  2. const WASM_URL = 'wasm.wasm';
  3. var wasm;
  4. if ('instantiateStreaming' in WebAssembly) {
  5. WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
  6. wasm = obj.instance;
  7. go.run(wasm);
  8. })
  9. } else {
  10. fetch(WASM_URL).then(resp =>
  11. resp.arrayBuffer()
  12. ).then(bytes =>
  13. WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
  14. wasm = obj.instance;
  15. go.run(wasm);
  16. })
  17. )
  18. }

If you have used explicit exports, you can call them by invoking them under thewasm.exports namespace. See the export directory in the examples for anexample of this.

In addition to the JavaScript, it is important the wasm file is served with theContent-Typeheader set to application/wasm. Without it, most browsers won’t run it.

  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. "strings"
  6. )
  7. const dir = "./html"
  8. func main() {
  9. fs := http.FileServer(http.Dir(dir))
  10. log.Print("Serving " + dir + " on http://localhost:8080")
  11. http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  12. resp.Header().Add("Cache-Control", "no-cache")
  13. if strings.HasSuffix(req.URL.Path, ".wasm") {
  14. resp.Header().Set("content-type", "application/wasm")
  15. }
  16. fs.ServeHTTP(resp, req)
  17. }))}

This simple server serves anything inside the ./html directory on port8080, setting any *.wasm files Content-Type header appropriately.

For development purposes (only!), it also sets the Cache-Control headerso your browser doesn’t cache the files. This is useful while developing, toensure your browser displays the newest wasm when you recompile.

In a production environment you probably wouldn’t want to set theCache-Control header like this. Caching is generally beneficial for endusers.

Further information on the Cache-Control header can be found here: