Go e WebAssembly
Go per WebAssembly
Go supporta la compilazione a WebAssembly nativamente dal Go 1.11. Esistono due approcci: il compilatore standard e TinyGo (per bundle più piccoli).
Compilare con Go Standard
GOOS=js GOARCH=wasm go build -o main.wasm main.go
Serve anche il file di supporto wasm_exec.js:
# Copiare il runtime JS dalla distribuzione Go
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
Esempio Base
File main.go:
package main
import (
"fmt"
"syscall/js"
)
// Funzione Go esposta a JavaScript
func add(this js.Value, args []js.Value) interface{} {
a := args[0].Int()
b := args[1].Int()
return a + b
}
// Funzione per manipolare il DOM
func setTitle(this js.Value, args []js.Value) interface{} {
title := args[0].String()
document := js.Global().Get("document")
document.Set("title", title)
return nil
}
func main() {
fmt.Println("Go WebAssembly inizializzato!")
// Registrare funzioni nel namespace globale JS
js.Global().Set("goAdd", js.FuncOf(add))
js.Global().Set("goSetTitle", js.FuncOf(setTitle))
// Mantenere il programma in esecuzione
select {}
}
Pagina HTML
<!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((result) => {
go.run(result.instance);
// Le funzioni Go sono ora disponibili
console.log(goAdd(10, 20)); // 30
goSetTitle("Titolo da Go!");
});
</script>
</body>
</html>
Il Package syscall/js
Il package syscall/js fornisce accesso completo alle API JavaScript da Go.
package main
import "syscall/js"
func main() {
// Accedere a oggetti globali
window := js.Global()
document := window.Get("document")
console := window.Get("console")
// Chiamare metodi JS
console.Call("log", "Ciao da Go!")
// Creare elementi DOM
div := document.Call("createElement", "div")
div.Set("textContent", "Creato da Go!")
div.Get("style").Set("color", "blue")
document.Get("body").Call("appendChild", div)
// Event listener
btn := document.Call("getElementById", "myButton")
callback := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
event := args[0]
console.Call("log", "Click!", event.Get("target"))
return nil
})
btn.Call("addEventListener", "click", callback)
// Fetch API
promise := window.Call("fetch", "/api/data")
promise.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
response := args[0]
response.Call("json").Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
data := args[0]
console.Call("log", "Dati ricevuti:", data)
return nil
}))
return nil
}))
select {}
}
TinyGo per Bundle Più Piccoli
Il compilatore Go standard produce binari Wasm di diversi MB (include il runtime Go e il garbage collector completo). TinyGo riduce drasticamente la dimensione.
# Installare TinyGo
brew install tinygo # macOS
# Compilare con TinyGo
tinygo build -o main.wasm -target wasm ./main.go
Confronto dimensioni tipiche:
| Compilatore | Dimensione .wasm |
|---|---|
| Go standard | ~2-10 MB |
| TinyGo | ~50-500 KB |
TinyGo usa il proprio wasm_exec.js (diverso da quello di Go standard):
# Copiare il wasm_exec.js di TinyGo
cp "$(tinygo env TINYGOROOT)/targets/wasm_exec.js" .
Esempio con TinyGo
package main
import "syscall/js"
//export multiply
func multiply(a, b int) int {
return a * b
}
// TinyGo supporta anche js.FuncOf
func registerCallbacks() {
js.Global().Set("fibonacci", js.FuncOf(
func(this js.Value, args []js.Value) interface{} {
n := args[0].Int()
return fib(n)
},
))
}
func fib(n int) int {
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}
func main() {
registerCallbacks()
select {}
}
Limitazioni
- Dimensione del binario: Go standard produce binari grandi perché include runtime e GC. Usare TinyGo per mitigare.
- Goroutine: funzionano ma con limitazioni. Il modello di concorrenza Go si mappa su un singolo thread Wasm.
- Reflection: TinyGo ha supporto limitato per la reflection.
- CGo: non supportato nel target Wasm.
syscall/jssolo per browser: il package funziona solo conGOOS=js, non con WASI.- Nessun networking diretto: le operazioni di rete devono passare attraverso le API JavaScript (Fetch, WebSocket).
- Performance GC: il garbage collector Go aggiunge overhead rispetto a linguaggi senza GC come Rust o C.
Go con WASI (senza browser)
Da Go 1.21, esiste anche il target WASI:
GOOS=wasip1 GOARCH=wasm go build -o main.wasm main.go
# Eseguire con un runtime WASI
wasmtime main.wasm
wasmer run main.wasm
Questo target non richiede wasm_exec.js ed è pensato per ambienti server-side.