00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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/js solo per browser: il package funziona solo con GOOS=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.