Jul 6, 2019

(Tiny)Go to WebAssembly

Gophers not only look cute but also they are powerful.

I :heart: statically typed languages and Golang is statically typed too.

Golang is syntactically similar to C, but with memory safety, Garbage Collection and CSP-style concurrency. - Wikipedia

We can compile Go into WebAssembly. This means we can write channels in Golang and run them on the browser.

WebAssembly in Golang is still in its early days. :unicorn:

Golang is very simple to write. Thus it is easy to compile the existing application straight into WebAssembly modules provided they don't have any file path or system level features which will not be available for WebAssembly during its runtime.

Hello World

Create a folder called go-wasm with child folders out and go;

 go-wasm/
 |__ out/
 |__ go/

Create a file called main.go inside the go folder and enter the following contents:

package main

func main() {
   println("Hello World!!!")
}

Run the go file with go run go/main.go. This will print Hello World. Ain't that easy.

Now we can compile this into WebAssembly module and run as a WebAssembly.

GOOS=js GOARCH=wasm go build -o out/main.wasm go/main.go

This will generate the main.wasm inside the out folder. Unlike Rust, Golang does not generate any binding file. We can use the binding file available from the TinyGo's official repository here.

Download the file and move it inside the out directory.

Create an index.html file inside the out directory and add the following contents:

<!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()
      WebAssembly.instantiateStreaming(
        fetch('main.wasm'),
        go.importObject
      ).then((res) => {
        go.run(res.instance)
      })
    </script>
  </body>
</html>

The index.html loads the wasm_exec.js file. Note that this file works for both Browser and NodeJS environment. It exports a Go object.

Then we add a local script. Inside the script we do the following:

Run the local server from the folder to see the Hello World.

But wait, we are writing Golang. Golang makes it fairly easy to write a simple WebServer in Golang. Let us write one.

Create a file called webServer.go in the root directory with the following contents.

package main

import (
        "log"
        "net/http"
        "strings"
)

const dir = "./out"

func main() {
        fs := http.FileServer(http.Dir(dir))
        log.Print("Serving " + dir + " on http://localhost:8080")
        http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
                resp.Header().Add("Cache-Control", "no-cache")
                if strings.HasSuffix(req.URL.Path, ".wasm") {
                        resp.Header().Set("content-type", "application/wasm")
                }
                fs.ServeHTTP(resp, req)
        }))
}

This serves the files inside the out folder. Now run the server using go run webServer.go. :tada: Isn't that easy?

Head over to the http://localhost:8080 and open the console to see the awesome Hello World.

The directory structure should look like below.

├── go
│   └── main.go
└── out
    ├── index.html
    ├── wasm_exec.js
    └── main.wasm

It is very important to note the file sizes of the generated WebAssembly module. And it is whooping 1.3MB for just a Hello World. That is huge right.

-rw-r--r--  1 sendilkumar  staff   482B Jul  5 23:20 index.html
-rwxr-xr-x  1 sendilkumar  staff   1.3M Jul  5 23:19 main.wasm
-rw-r--r--  1 sendilkumar  staff    13K Jul  5 23:18 wasm_exec.js

Any performance WebAssembly might give, it is not desirable to have it if the modules are huge.

Don't worry we have Teeny TinyGO.

Tiny Go

The TinyGo is a project built to take Golang into microcontrollers and modern web browsers. They have a brand new compiler that compiles based on LLVM. With TinyGo we can generate teeny tiny libraries that are optimized to execute in the chips.

Check out how to install TinyGo here.

Once installed, you can use TinyGo to compile any Golang code. We can compile Golang into WebAssembly module using the following command.

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

Now go to the browser and refresh the page to see Hello World still printed.

The most important thing is the generated WebAssembly module is just 3.8K :tada: :tada: :tada:

-rw-r--r--  1 sendilkumar  staff   482B Jul  5 23:20 index.html
-rwxr-xr-x  1 sendilkumar  staff   3.8K Jul  5 23:29 main.wasm
-rw-r--r--  1 sendilkumar  staff    13K Jul  5 23:18 wasm_exec.js

Since we have LLVM underneath we can tweak it further using the -opt flag. The maximum size optimization is obtained with the -opt=z flag and TinyGo uses this by default. Because those tiny devices have limited memory.

Keep tuning in the next post, let us re-create the classic Dev's offline page using TinyGo and WebAssembly.

I hope this gives you a motivation to start your awesome WebAssembly journey. If you have any questions/suggestions/feel that I missed something feel free to add a comment.

You can follow me on Twitter.


Up Next