Skip to main content

Build WebAssembly Apps in Go

Shenzhen, China

Github

Initialize Project

go mod init go-wasm
go mod tidy

Hello World

package main

import "fmt"

func main() {
	fmt.Println("Go WebAssembly")
}

Build for WebAssambly

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

Run Application (Node.js)

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
npm install -g live-server
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Go WebAssembly</title>
    <script src="/wasm_exec.js"></script>
    <script>
      const go = new Go();
      WebAssembly.instantiateStreaming(fetch("/main.wasm"), go.importObject)
          .then((result) => {
            go.run(result.instance);
          })
    </script>
  </head>
  <body>
  </body>
</html>

Start live-server and visit localhost:8080:

Build WebAssembly Apps in Go

Manipulate JS-DOM

package main

import (
	"fmt"
	"syscall/js"
)

func main() {
	fmt.Println("Go WebAssembly")

	document := js.Global().Get("document")

	hello := document.Call("createElement", "h1")
	hello.Set("innerText", "Go WebAssembly")
	document.Get("body").Call("appendChild", hello)
}
GOOS=js GOARCH=wasm go build -o main.wasm

Start live-server and visit localhost:8080:

Build WebAssembly Apps in Go

Expose Functions

package main

import (
	"fmt"
	"syscall/js"
)

func exposedFunction(this js.Value, inputs []js.Value) interface{} {
	fmt.Println("Exposed Function Executed!")
	return nil
}

func main() {
	// channel to keep the wasm running
	c := make(chan int)
	fmt.Println("Go WebAssembly")

	js.Global().Set("exposedFunction", js.FuncOf(exposedFunction))

	document := js.Global().Get("document")

	hello := document.Call("createElement", "h1")
	hello.Set("innerText", "Go WebAssembly")
	document.Get("body").Call("appendChild", hello)
	// value for c is never send
	<-c
}

Function can be called from your browser console:

Build WebAssembly Apps in Go

Optimizing Filesize

TinyGo is a new compiler for the Go programming language. TinyGo focuses on compiling code written in Go, but for smaller kinds of systems:

  • The Go compiler and tools (from golang.org) are the reference implementation of the Go programming language. They are primarily intended for server side programming but also used for command line tools and other purposes.
  • The TinyGo project implements the exact same programming language. However, TinyGo uses a different compiler and tools to make it suited for embedded systems and WebAssembly. It does this primarily by creating much smaller binaries and targeting a much wider variety of systems.

TinyGo can be installed on a variety of host systems. On Arch you can get the compiler directly through Pacman:

sudo pacman -S tinygo
tinygo build -o main-tiny.wasm -target wasm ./main.go
ls -la
310393 Dec 26 20:05 main-tiny.wasm
2065184 Dec 26 19:47 main.wasm

When running the new, tiny version of our program we get an Type Error:

TypeError: import object field 'wasi_unstable' is not an Object

This is because we copied in the following file from the Go compiler:

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

But here we need to swap it for the version from TinyGo:

cp $(tinygo env TINYGOROOT)/targets/wasm_exec.js ./wasm_exec_tiny.js

And link it into our HTML file instead.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Go WebAssembly</title>
 
    <!-- GO COMPILER -->
    <!--
    <script src="/wasm_exec.js"></script>
    <script>
      const go = new Go();
      WebAssembly.instantiateStreaming(fetch("/main.wasm"), go.importObject)
          .then((result) => {
            go.run(result.instance);
          })
    </script> -->
    
    <!-- TinyGo COMPILER -->
    <script src="/wasm_exec_tiny.js"></script>
    <script>
      const go = new Go();
      WebAssembly.instantiateStreaming(fetch("/main-tiny.wasm"), go.importObject)
          .then((result) => {
            go.run(result.instance);
          })
    </script>

  </head>
  <body>
  </body>
</html>