Go in WebAssembly

Go was early to the WebAssembly game, with the Go compiler producing wasm32 output alongside its regularly supported build targets. But the core Go tools have fallen behind. Instead, the alterative Go implementation called TinyGo seems to have taken the lead.

Available Implementations

  • Go supports browser-based WebAssembly
  • TinyGo supports wasm32-wasi as a build target
  • The Elements compiler may also support compiling browser-oriented Wasm

We have had the best luck with TinyGo.

Usage

With TinyGo, it is possible to compile most Go code into Wasm with WASI support. That means you can write Go code targeting the Fermyon Platform.

Pros and Cons

Things we like:

  • TinyGo works very well
  • Spin has full support for Go

We’re neutral about:

  • The resulting binary sizes start at around 300k, but can rapidly climb

Things we’re not big fans of:

  • Upstream (mainline) Go does not have WASI support
  • TinyGo is still missing (mostly reflection-based) features of Go

Example

All of our examples follow a documented pattern using common tools.

TinyGo can compile to wasm32-wasi, and TinyGo apps can be run on the Fermyon Platform. You will need to install TinyGo to do this example. Note that you also have to have Go installed

As with any Go project, the first step is to create a go.mod file:

module github.com/fermyon/example-go

go 1.23

Since Spin has a Go SDK which is nice and easy to use, we’ll fetch that and use it:

$ go get github.com/fermyon/spin-go-sdk

Next, create a simple Go program named main.go:

package main

import (
	"fmt"
	"net/http"

	spinhttp "github.com/fermyon/spin-go-sdk/http"
)

func main() {
	spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, World!")
	})
}

When it comes to compiling, though, we will need to use TinyGo instead of Go. This particular set of flags has produced the best results for us:

tinygo build -target=wasi -gc=leaking -o main.wasm main.go

The above will output a main.wasm file. A simple spin.toml for running the above in Spin looks like this:

spin_manifest_version = 2

[application]
name = "spin-go-hello"
version = "1.0.0"
description = "Hello world app."
authors = ["Fermyon Engineering <engineering@fermyon.com>"]

[[trigger.http]]
component = "hello"
route = "/"

[component.hello]
source = "main.wasm"
[component.hello.build]
command = "tinygo build -target=wasi -gc=leaking -o main.wasm main.go"

Note: we’ve set the hello component’s build command to be the tinygo invocation above, so that spin build will run it from now on

From there, it’s just a matter of using spin up to start the server. As usual, we can test using a web browser or Curl:

$ curl localhost:3000/
Hello, World!

Learn More

Here are some great resources: