This page looks best with JavaScript enabled

Introduction to WebAssembly with Go

 ·   ·  ☕ 6 min read

I will start this post with a quote from webassembly.org:

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications.

webassembly.org

That’s a lot of technical jargon. In very simple terms, Wasm provides a way to run code written in multiple languages on the web at near-native speed. Where multiple languages refers to all these languages.

Basically what web assembly allows us to write code in any supported favorite language, compile it to binary (typically with the extension .wasm), and call it from JavaScript code in the browser.

This is going to break the stereotype of developing frontends in JavaScript, which has been the language for frontend. With the advent of frameworks like Vugu, and Vecty things seems to be changing in regards with Go. They are in fact in active development. How about DOM binding in Go? How about making games?

via GIPHY

WASM has been developed collaboratively by Mozilla, Microsoft, Google, Apple and W3C (I see W3C as similar to CNCF, but for WWW only). So, it isn’t going anywhere for sure.

Web Assembly in Go is still experimental API. And the API may change in future, which means it is backward incompatible.

Current Usage

According to Wikipedia:

WebAssembly became a World Wide Web Consortium recommendation on 5 December 2019 and, alongside HTML, CSS, and JavaScript, is the fourth language to run natively in browsers.

Wikipedia

And regarding the support in browsers, this is what Wikipedia has to say:

As of May 2020, 91.44% of installed browsers (91.56% of desktop browsers and 92.94% of mobile browsers) support WebAssembly.

Wikipedia

That’s too much of information for today. Let’s do some quick example.

Wasm and Go

As I am a Go fan, let’s do a Hello WASM! application with it. WebAssembly works with Go 1.11 and above. I am writing along this blog post with go verison 1.14.2.

Hello WASM

Powerpuff recepie for WASM
Powerpuff recepie for WASM

There are 4 ingredients we need.

  • wasm binary which we are going to compile
  • wasm_exec.js which will hold the connector code between wasm binary and HTML file
  • an HTML page to hook the wasm_exec.js file to
  • a web server to host these three

Out of all these, only the binary is going to be go specific. Rest are generic to all the supported languages. Even that you are familiar with JavaScript, you can find the documentation on MDN to write your own code to glue things together. Anyway, let’s deal with the binary:

1
2
3
4
5
6
7
8
9
package main

import (
    "fmt"
)

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

Normally to generate an executable, I would have done go build -o hello.wasm hello_wasm.go (frankly speaking, I just do go build on my Fedora). But to create a WASM binary we gotta set some environment variables. And those variables are GOOS=js GOARCH=wasm. So the full command would be something like this:

GOOS=js GOARCH=wasm go build -o hello.wasm hello_wasm.go

Next, copy wasm_exec.js to cwd from go installation directory:

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

If you are somehow not able to find the files there, like me, get it from https://github.com/golang/go/tree/master/misc/wasm. Just to make sure, this piece of code is from master branch, and you are expected to use it with latest version of golang. Otherwise go to specific branch and then download.

curl https://raw.githubusercontent.com/golang/go/master/misc/wasm/wasm_exec.js -O

Use this HTML snippet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Go wasm</title>
</head>
<body>
  <script src="wasm_exec.js"></script>
  <script>
    const go = new Go();
    let mod, inst;
    WebAssembly.instantiateStreaming(fetch("hello.wasm"), go.importObject).then((result) => {
      mod = result.module;
      inst = result.instance;
    }).catch((err) => {
      console.error(err);
    });

  </script>
</body>
</html>

WebAssembly.instantiateStreaming takes in our binary and import object. And returns WebAssembly.Module and a WebAssembly.Instance object, which in above example is assigned to mod and inst variable respectively.

I’m going to ditch golang and start a web server with Python. 🤭 It’s simple.

1
python -m http.server

This will start serving files from the current directory. Head over to http://127.0.0.1:8000. Once the wasm_exec.html is loaded, you will see a button named Run.

Run button's output when everything is set correctly.
Run button's output when everything is set correctly.

Note: If you don’t see anything in the console, check if the correct file is linked in the JavaScript file. The default one is “test.wasm” in the HTML file, but we compiled hello.wasm.

Exporting a Go function to JavaScript

syscall/js is at the heart of all the operations between JavaScript and Go. And this is library agnostic.

While going through the documentation you will find that there are only a few types exposed. We’ll will focus on Global Func and Value (and their methods) today.

js.Global() can be used to get JavaScript global object. From which we can get the document and eventually everything in the DOM.

This is a JavaScript function which looks for an element with ID foo and sets the text to bar.

1
2
3
4
5
function barFunc() {
    let para = document.createElement("p");
    para.innerHTML = "bar";
    document.getElementsByTagName("body")[0].appendChild(para);
}

Equivalent code in Go would look like this:

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

import "syscall/js"

func main() {
    js.Global().Set("barFunc", js.FuncOf(barFunc))

    // Prevent main from exiting
    select {}
}

func barFunc(this js.Value, inputs []js.Value) interface{} {
    document := js.Global().Get("document")
    p := document.Call("createElement", "p")
    p.Set("innerHTML", inputs[0])
    document.Get("body").Call("appendChild", p)

    return nil
}

Note that signature has to be like that. This will make barFunc available to global namespace.

Exposed Go function available in JavaScript
Exposed Go function available in JavaScript

Moreover, there is a whole lot of resources available regarding Go and WASM at https://github.com/golang/go/wiki/WebAssembly

We are not limited to DOM manipulation with WASM. We can use any existing browser API such as using network calls, LocalStorage, using WebGL etc. I would recommend this GopherCon video by Johan Brandhorst who is a contributor to golang codebase.

Security Considerations

This was an eagle eye introduction to WebAssembly in Go. Let me end this post with some security concerns of WebAssembly.

WebAssembly has been criticized for allowing greater ease of hiding the evidence for malware writers, scammers and phishing attackers; WebAssembly is only present on the user’s machine in its compiled form, which “makes malware detection difficult”.

The speed and concealability of WebAssembly have led to its use in hidden crypto mining on the website visitor’s device. Coinhive, a now defunct service facilitating cryptocurrency mining in website visitors' browsers, claims their “miner uses WebAssembly and runs with about 65% of the performance of a native Miner.”

A June 2019 study from the Technische Universität Braunschweig, analyzed the usage of WebAssembly in the Alexa top 1 million websites and found the prevalent use was for malicious crypto mining, and that malware accounted for more than half of the WebAssembly-using websites studied.

Share on

Santosh Kumar
WRITTEN BY
Santosh Kumar
Fullstack Developer at Method Studios