Go and TinyGo
19.1 Standard Go
Go binaries are large (2+ MB) because they include the runtime and GC. Go 1.24 added //go:wasmexport to export functions for WASI reactors.
// main.go: browser target (GOOS=js GOARCH=wasm)
//go:build js && wasm
package main
import (
"fmt"
"syscall/js"
)
func add(this js.Value, args []js.Value) any {
return args[0].Int() + args[1].Int()
}
func main() {
fmt.Println("Go Wasm loaded")
js.Global().Set("goAdd", js.FuncOf(add))
doc := js.Global().Get("document")
p := doc.Call("createElement", "p")
p.Set("textContent", "Hello from Go")
doc.Get("body").Call("appendChild", p)
select {} // keep the program running
}GOOS=js GOARCH=wasm go build -o main.wasm
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" . # glue (misc/wasm in older Go)
GOOS=wasip1 GOARCH=wasm go build -o app.wasm # WASI preview 1 target (Go 1.21+)
wasmtime app.wasm<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then(r => go.run(r.instance));
</script>19.2 TinyGo
TinyGo produces much smaller binaries (tens of KB), is popular for plugins (Envoy proxy-wasm, Extism, Spin), and supports a subset of the stdlib and reflection.
tinygo build -o small.wasm -target wasm ./main.go # browser (much smaller output)
tinygo build -o app.wasm -target wasip1 ./main.go
tinygo build -o comp.wasm -target wasip2 ./main.go # components