WebAssembly (WASM) permite rodar código compilado diretamente no browser com performance próxima ao nativo. E o Go tem suporte oficial a WebAssembly desde o Go 1.11 — você pode compilar qualquer programa Go para WASM e executá-lo em qualquer navegador moderno.
Neste guia, você vai aprender a compilar Go para WebAssembly, interagir com o DOM, usar TinyGo para binários menores e construir uma aplicação prática que roda 100% no browser.
Por que Go + WebAssembly?
Se você já programa em Go para backend ou APIs REST, WebAssembly abre a porta para reutilizar lógica no frontend. Alguns casos de uso reais:
- Validação de dados — a mesma lógica de validação no servidor e no cliente
- Processamento de dados — parsing de CSV, cálculos financeiros, criptografia
- Ferramentas interativas — editores, calculadoras, visualizações
- Jogos simples — engines leves e simulações
- Portabilidade — bibliotecas Go existentes rodando no browser
A vantagem sobre JavaScript puro é a performance em operações CPU-intensive e a possibilidade de compartilhar código entre backend e frontend.
Configurando o Ambiente
Para compilar Go para WASM, você não precisa instalar nada extra — o compilador padrão do Go já suporta o target GOOS=js GOARCH=wasm. Vamos começar:
// main.go — nosso primeiro programa Go para o browser
package main
import (
"fmt"
"syscall/js"
)
func main() {
fmt.Println("Go rodando no browser via WebAssembly!")
// Mantém o programa rodando (sem isso, o WASM termina imediatamente)
select {}
}
Para compilar, use as variáveis de ambiente GOOS e GOARCH:
# Compilar Go para WebAssembly
GOOS=js GOARCH=wasm go build -o main.wasm main.go
# Copiar o arquivo JavaScript de suporte do Go
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
O arquivo wasm_exec.js é o runtime JavaScript que o Go precisa para funcionar no browser. Ele configura o ambiente, gerencia goroutines e faz a ponte entre Go e JavaScript.
Criando a Página HTML
Agora precisamos de uma página HTML que carrega o WASM:
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="utf-8">
<title>Go WebAssembly Demo</title>
<script src="wasm_exec.js"></script>
</head>
<body>
<h1>Go + WebAssembly</h1>
<div id="resultado"></div>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(
fetch("main.wasm"),
go.importObject
).then((result) => {
go.run(result.instance);
});
</script>
</body>
</html>
Para servir localmente, você pode usar qualquer servidor HTTP. O próprio Go serve:
# Servidor HTTP simples em Go para testar
go run golang.org/x/tools/cmd/httpserve@latest -addr :8080 .
Abra http://localhost:8080 e veja “Go rodando no browser via WebAssembly!” no console do browser.
Interagindo com o DOM
O pacote syscall/js permite manipular o DOM, chamar funções JavaScript e expor funções Go para o JavaScript. Vamos criar uma calculadora interativa:
package main
import (
"fmt"
"math"
"syscall/js"
)
// calcularIMC recebe peso e altura do JavaScript e retorna o IMC
func calcularIMC(this js.Value, args []js.Value) interface{} {
if len(args) < 2 {
return "Informe peso (kg) e altura (m)"
}
peso := args[0].Float()
altura := args[1].Float()
if altura <= 0 {
return "Altura deve ser maior que zero"
}
imc := peso / math.Pow(altura, 2)
var classificacao string
switch {
case imc < 18.5:
classificacao = "Abaixo do peso"
case imc < 25:
classificacao = "Peso normal"
case imc < 30:
classificacao = "Sobrepeso"
default:
classificacao = "Obesidade"
}
resultado := fmt.Sprintf("IMC: %.1f — %s", imc, classificacao)
// Atualizar o DOM diretamente do Go
document := js.Global().Get("document")
elemento := document.Call("getElementById", "resultado")
elemento.Set("textContent", resultado)
return resultado
}
func main() {
// Expor a função Go para o JavaScript global
js.Global().Set("calcularIMC", js.FuncOf(calcularIMC))
fmt.Println("Funções Go registradas no browser!")
// Manter o programa ativo
select {}
}
Agora o JavaScript pode chamar calcularIMC(80, 1.75) diretamente. A função Go manipula o DOM, faz o cálculo e atualiza a página — tudo rodando em WebAssembly.
Callbacks e Eventos do DOM
Você pode registrar event listeners diretamente do Go:
func registrarEventos() {
document := js.Global().Get("document")
// Registrar click handler em um botão
botao := document.Call("getElementById", "btn-calcular")
botao.Call("addEventListener", "click", js.FuncOf(
func(this js.Value, args []js.Value) interface{} {
// Ler valores dos inputs
peso := document.Call("getElementById", "peso").Get("value").String()
altura := document.Call("getElementById", "altura").Get("value").String()
fmt.Printf("Calculando IMC: peso=%s, altura=%s\n", peso, altura)
// ... processar
return nil
},
))
}
Essa abordagem é poderosa para criar interfaces interativas complexas. Mas repare: o código fica verboso comparado ao JavaScript. É aqui que o Go + WASM brilha em lógica pesada, não em manipulação simples do DOM.
TinyGo: Binários 90% Menores
O compilador padrão do Go gera binários WASM relativamente grandes (2-10 MB) porque inclui o runtime completo com garbage collector, scheduler de goroutines e todo o sistema de tipos. Para aplicações web, isso é um problema.
TinyGo é um compilador alternativo otimizado para targets pequenos como WebAssembly e microcontroladores:
# Instalar TinyGo
# macOS
brew install tinygo
# Compilar com TinyGo (binários muito menores!)
tinygo build -o main.wasm -target wasm main.go
Comparação de tamanhos típicos:
| Compilador | Binário WASM | Tempo de carga |
|---|---|---|
| Go padrão | ~5 MB | ~2s |
| TinyGo | ~200 KB | ~50ms |
| TinyGo + gzip | ~80 KB | ~20ms |
A diferença é enorme. TinyGo consegue isso removendo reflection, simplificando o GC e otimizando agressivamente o código morto. A desvantagem? Nem todos os pacotes da standard library são suportados. Mas para WASM no browser, a maioria dos casos funciona perfeitamente.
Exemplo Prático: Validador de CPF no Browser
Vamos criar algo útil — um validador de CPF que roda inteiramente no browser:
package main
import (
"fmt"
"strconv"
"strings"
"syscall/js"
)
// validarCPF implementa o algoritmo de validação de CPF
func validarCPF(cpf string) bool {
// Remover pontos e traço
cpf = strings.ReplaceAll(cpf, ".", "")
cpf = strings.ReplaceAll(cpf, "-", "")
if len(cpf) != 11 {
return false
}
// Verificar se todos os dígitos são iguais (ex: 111.111.111-11)
todosIguais := true
for i := 1; i < len(cpf); i++ {
if cpf[i] != cpf[0] {
todosIguais = false
break
}
}
if todosIguais {
return false
}
// Calcular primeiro dígito verificador
soma := 0
for i := 0; i < 9; i++ {
digito, _ := strconv.Atoi(string(cpf[i]))
soma += digito * (10 - i)
}
resto := soma % 11
primeiroDigito := 0
if resto >= 2 {
primeiroDigito = 11 - resto
}
// Calcular segundo dígito verificador
soma = 0
for i := 0; i < 10; i++ {
digito, _ := strconv.Atoi(string(cpf[i]))
soma += digito * (11 - i)
}
resto = soma % 11
segundoDigito := 0
if resto >= 2 {
segundoDigito = 11 - resto
}
// Comparar com os dígitos informados
digitoVerif1, _ := strconv.Atoi(string(cpf[9]))
digitoVerif2, _ := strconv.Atoi(string(cpf[10]))
return primeiroDigito == digitoVerif1 && segundoDigito == digitoVerif2
}
// validarCPFJS expõe a validação para JavaScript
func validarCPFJS(this js.Value, args []js.Value) interface{} {
if len(args) < 1 {
return "Informe um CPF"
}
cpf := args[0].String()
valido := validarCPF(cpf)
document := js.Global().Get("document")
resultado := document.Call("getElementById", "resultado")
if valido {
resultado.Set("innerHTML", fmt.Sprintf(
"<span style='color:green'>✓ CPF %s é válido</span>", cpf))
} else {
resultado.Set("innerHTML", fmt.Sprintf(
"<span style='color:red'>✗ CPF %s é inválido</span>", cpf))
}
return valido
}
func main() {
js.Global().Set("validarCPF", js.FuncOf(validarCPFJS))
fmt.Println("Validador de CPF Go+WASM carregado!")
select {}
}
Esse é um caso perfeito para WASM: a mesma lógica de validação que você usa no backend Go agora roda no browser, sem duplicar código em JavaScript.
Go WASM vs Outras Linguagens
Como o Go se compara a Rust e C++ para WebAssembly?
| Aspecto | Go | Rust | C/C++ |
|---|---|---|---|
| Facilidade | ★★★★★ | ★★★ | ★★ |
| Tamanho binário | ★★ (★★★★ com TinyGo) | ★★★★★ | ★★★★ |
| Performance | ★★★★ | ★★★★★ | ★★★★★ |
| Ecossistema WASM | ★★★ | ★★★★★ | ★★★ |
| Interop com JS | ★★★ | ★★★★ | ★★ |
Go ganha na simplicidade: qualquer desenvolvedor Go consegue gerar WASM em minutos. Rust oferece melhor performance e binários menores, mas com curva de aprendizado muito maior. Se você já domina Go, WASM é uma extensão natural do seu skillset.
Boas Práticas para Go + WASM
- Use TinyGo para produção — binários menores significam carregamento mais rápido
- Minimize a manipulação do DOM — use Go para lógica pesada, JavaScript para UI simples
- Gerencie memória —
js.FuncOfcria funções que precisam deRelease()quando não mais necessárias - Sirva com gzip/brotli — WASM comprime muito bem (60-80% de redução)
- Use Web Workers — rode o WASM em uma thread separada para não bloquear a UI
- Teste no Go padrão primeiro — use
GOOS=jsapenas no build final, teste a lógica com testes normais
Quando Usar (e Quando Não Usar)
Use Go + WASM quando:
- Precisa compartilhar lógica entre backend e frontend
- Tem processamento CPU-intensive no browser
- Já tem código Go que quer reutilizar no cliente
- Está construindo ferramentas/utilitários web (como o validador de CPF acima)
Não use quando:
- A aplicação é puramente UI/DOM-heavy
- O tamanho do bundle é crítico e TinyGo não suporta os pacotes necessários
- Um framework JavaScript resolve mais facilmente
Próximos Passos
WebAssembly com Go é uma tecnologia madura e prática. Se você quer criar aplicações web modernas sem depender de frameworks JavaScript pesados, considere também a combinação de Go com HTMX para interfaces dinâmicas server-side.
Para se aprofundar, explore os módulos Go para organizar seus projetos WASM, e confira como tratar erros corretamente nas funções que expõe ao JavaScript — erros silenciosos no WASM são difíceis de debugar.
Se você está começando com Go, nosso guia de primeiros passos cobre tudo que você precisa para configurar o ambiente. E para quem quer entender como Go se compara com outras linguagens, temos comparações com Rust, Python e Java.