syscall/js: Go no Navegador
O pacote syscall/js permite que o Go WASM converse com JavaScript: nós DOM, fetch, timers e callbacks de JS para Go.
Busque em todas as páginas da documentação
O pacote syscall/js permite que o Go WASM converse com JavaScript: nós DOM, fetch, timers e callbacks de JS para Go.
É a peça distintiva do alvo GOOS=js GOARCH=wasm.
Mantenha-o atrás de tags de build para que módulos WASI do lado do servidor nunca importem chamadas de sistema exclusivas do navegador.
O Go WASM no navegador compila com GOOS=js e GOARCH=wasm.
A página host carrega wasm_exec.js mais o seu main.wasm.
Funções Go registradas com js.FuncOf recebem eventos JS; js.Global() alcança globais do navegador como document e window.
Como main sairia de outra forma, UIs de longa duração bloqueiam com select {}.
Cartão de receita de referência rápida - pronto para copiar e colar.
//go:build js && wasm
package main
import (
"syscall/js"
)
func main() {
doc := js.Global().Get("document")
btn := doc.Call("createElement", "button")
btn.Set("textContent", "Clique em mim")
btn.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
js.Global().Get("console").Call("log", "clicado de Go")
return nil
}))
doc.Get("body").Call("appendChild", btn)
select {}
}Quando usar isso:
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Go WASM Demo</title>
<script src="wasm_exec.js"></script>
</head>
<body>
<div id="out"></div>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject)
.then((result) => {
go.run(result.instance);
});
</script>
</body>
</html>main.go:
//go:build js && wasm
package main
import (
"strconv"
"syscall/js"
)
func main() {
counter := 0
doc := js.Global().Get("document")
out := doc.Call("getElementById", "out")
btn := doc.Call("createElement", "button")
btn.Set("innerText", "Incrementar")
handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
counter++
out.Set("innerText", "contagem="+strconv.Itoa(counter))
return nil
})
btn.Call("addEventListener", "click", handler)
out.Call("appendChild", btn)
select {}
}Compilar e servir:
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
GOOS=js GOARCH=wasm go build -o main.wasm .
python3 -m http.server 8080O que isso demonstra:
js.FuncOf envolve closures Go como funções chamáveis por JS.js.Value, não cgo.wasm_exec.js fornece as importações que o módulo Go WASM requer.wasm_exec.js.js.Value é um identificador opaco para um valor JS; Get, Set e Call marshalizam através da fronteira.js.Value; libere callbacks de longa duração com js.Func.Release() ao remover listeners.| Tag de arquivo | Compilado quando | Papel |
|---|---|---|
//go:build js && wasm | Alvo do navegador | DOM, entrada syscall/js |
//go:build !js || !wasm | Nativo + WASI | Lógica compartilhada sem importações JS |
| padrão (sem tag) | Todos os alvos não marcados | Pacotes portáteis |
// Exemplo de Fetch (o host deve fornecer fetch nos globais)
promise := js.Global().Call("fetch", "/api/data")
// Em seguida, encadeie .then com manipuladores js.FuncOf - espelhe o estilo de promessa JS// Libere callbacks registrados em páginas de longa duração para evitar vazamentos.
var fn js.Func
fn = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
fn.Release()
return nil
})Esquecer select {} - main retorna, o runtime sai e os callbacks nunca disparam. Correção: bloqueie em select {} ou em um canal alimentado por lógica de desligamento.
Importar syscall/js em builds WASI - Quebra a compilação GOOS=wasip1 ou incha módulos headless. Correção: tags de arquivo estritas js && wasm.
Servir sem o tipo MIME correto - Alguns servidores estáticos servem .wasm como application/octet-stream. Correção: use application/wasm ou instantiateStreaming falha; configure o mapa MIME do servidor.
Vazamento de callbacks js.Func - Registrar novamente listeners sem Release retém closures Go. Correção: chame Release() ao remover nós DOM ou navegar para longe.
Esperar rede stdlib completa - A segurança do navegador molda net/http; muitos padrões precisam de fetch via JS. Correção: envolva fetch ou mantenha o código de rede em workers WASI.
Tamanhos grandes de bundle - Binários Go js/wasm são de vários megabytes. Correção: meça antes de confirmar; considere TinyGo ou computação do host fora da thread.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
wasip1/wasm no navegador via polyfill WASI | Computação intensiva, sem DOM direto | Você precisa de controle DOM granular |
| UI TypeScript/React + plugin WASM | UX de produção com núcleo de computação Go | Equipe quer código de UI Go puro |
Alvo wasm do TinyGo | WASM de navegador menor | Você precisa das bibliotecas completas de reflect/generics |
| Linguagens com GC WebAssembly (hosts futuros) | Host suporta a proposta de GC | Você precisa enviar hoje em todos os navegadores |
Sim para o alvo js/wasm, mas trate-o como uma ponte especializada.
Padrão para novos trabalhos com wasip1, a menos que o acesso DOM seja explícito.
Copie de $(go env GOROOT)/misc/wasm/wasm_exec.js.
Versione-o com sua toolchain Go para que as importações permaneçam correspondentes.
Sim. Marque todos os arquivos que importam syscall/js com js && wasm, ou coloque-os em um pacote exclusivo para navegador.
Exporte funções com js.Global().Set("goDoThing", js.FuncOf(...)) antes de bloquear.
Leia valores do JS através de args []js.Value.
Não em compilações js/wasm.
Mantenha testes de concorrência em pacotes linux/amd64 compartilhados com a lógica WASM.
Sim - a saída é roteada para o console do navegador através do shim do runtime JS.
Prefira logging estruturado para aplicativos reais.
Use os helpers js.CopyBytesToJS e js.CopyBytesToGo para mover dados através da fronteira sem cópias extras, sempre que possível.
Você pode executar WASM em Web Workers com um loader wasm_exec.js personalizado, mas APIs DOM não estão disponíveis em workers.
Arquiteture de acordo.
TinyGo suporta um subconjunto de alvos WASM; verifique sua documentação de interop JS para sua versão.
As formas da API diferem do comportamento da toolchain gc.
Não. Hosts WASM headless não fornecem shims JS.
Divida pacotes e imponha com regras de lint ou verificações de tag de build go vet.
Versões da Pilha: Esta página foi escrita para Go 1.26.x (GC padrão Green Tea, go fix modernizers - verifique o patch na compilação), chi (última - verifique na compilação), gin (última - verifique na compilação), echo (última - verifique na compilação), google.golang.org/grpc (última - verifique na compilação), sigs.k8s.io/controller-runtime (última - verifique na compilação), kubebuilder (última - verifique na compilação), tinygo (última - verifique os alvos de placa na compilação), wazero (última - verifique na compilação) e golangci-lint (última - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026