syscall/js: Go en el Navegador
El paquete syscall/js permite que Go WASM hable con JavaScript: nodos DOM, fetch, temporizadores y devoluciones de llamada de JS a Go.
Busca en todas las páginas de la documentación
El paquete syscall/js permite que Go WASM hable con JavaScript: nodos DOM, fetch, temporizadores y devoluciones de llamada de JS a Go.
Es la pieza distintiva del objetivo GOOS=js GOARCH=wasm.
Mantenlo detrás de etiquetas de compilación para que los módulos WASI del servidor nunca importen llamadas de sistema solo para el navegador.
Go WASM para el navegador se compila con GOOS=js y GOARCH=wasm.
La página anfitriona carga wasm_exec.js además de tu main.wasm.
Las funciones de Go registradas con js.FuncOf reciben eventos de JS; js.Global() accede a globales del navegador como document y window.
Dado que main de lo contrario saldría, las UI de larga duración se bloquean con select {}.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
//go:build js && wasm
package main
import (
"syscall/js"
)
func main() {
doc := js.Global().Get("document")
btn := doc.Call("createElement", "button")
btn.Set("textContent", "Click me")
btn.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
js.Global().Get("console").Call("log", "clicked from Go")
return nil
}))
doc.Get("body").Call("appendChild", btn)
select {}
}Cuándo usar esto:
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", "Increment")
handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
counter++
out.Set("innerText", "count="+strconv.Itoa(counter))
return nil
})
btn.Call("addEventListener", "click", handler)
out.Call("appendChild", btn)
select {}
}Compila y sirve:
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
GOOS=js GOARCH=wasm go build -o main.wasm .
python3 -m http.server 8080Lo que esto demuestra:
js.FuncOf envuelve cierres de Go como funciones invocables por JS.js.Value, no cgo.js.Value; libera las devoluciones de llamada de larga duración con js.Func.Release() al eliminar oyentes.wasm_exec.js.js.Value es un manejador opaco para un valor de JS; Get, Set y Call marcan el cruce del límite.js.Value; libera las devoluciones de llamada de larga duración con js.Func.Release() al eliminar oyentes.| Etiqueta de archivo | Compilado cuando | Rol |
|---|---|---|
//go:build js && wasm | Objetivo del navegador | DOM, entrada de syscall/js |
//go:build !js || !wasm | Nativo + WASI | Lógica compartida sin importaciones de JS |
| predeterminado (sin etiqueta) | Todos los objetivos sin etiqueta | Paquetes portátiles |
js.Value// Ejemplo de Fetch (el host debe proporcionar fetch en los globales)
promise := js.Global().Call("fetch", "/api/data")
// Luego encadena .then con manejadores de js.FuncOf - refleja el estilo de promesa de JS// Libera las devoluciones de llamada registradas en páginas de larga duración para evitar fugas.
var fn js.Func
fn = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
fn.Release()
return nil
})Olvidar select {} - main sale, el tiempo de ejecución termina y las devoluciones de llamada nunca se activan. Solución: bloquea en select {} o un canal alimentado por lógica de apagado.
Importar syscall/js en compilaciones WASI - Rompe la compilación GOOS=wasip1 o hincha los módulos headless. Solución: etiquetas de archivo estrictas js && wasm.
Servir sin el tipo MIME correcto - Algunos servidores estáticos sirven .wasm como application/octet-stream. Solución: usa application/wasm o instantiateStreaming fallará; configura el mapa MIME del servidor.
Fugas de devoluciones de llamada de js.Func - Volver a registrar oyentes sin Release retiene cierres de Go. Solución: llama a Release() al eliminar nodos DOM o al navegar.
Esperar la red stdlib completa - La seguridad del navegador da forma a net/http; muchos patrones necesitan fetch a través de JS. Solución: envuelve fetch o mantén el código de red en trabajadores WASI.
Tamaños de paquete grandes - Los binarios de Go js/wasm son de varios megabytes. Solución: mide antes de confirmar; considera TinyGo o computación del host fuera de hilo.
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
wasip1/wasm en el navegador a través de polyfill WASI | Cómputo intensivo, sin DOM directo | Necesitas control granular del DOM |
| UI TypeScript/React + plugin WASM | UX de producción con núcleo de cómputo Go | El equipo quiere código de UI puramente Go |
Objetivo wasm de TinyGo | WASM de navegador más pequeño | Necesitas las bibliotecas completas de reflect/generics |
| Lenguajes con GC WebAssembly (hosts futuros) | El host soporta la propuesta de GC | Debes enviar hoy en todos los navegadores |
Sí, para el objetivo js/wasm, pero trátalo como un puente especializado.
Por defecto, el nuevo trabajo debe ser wasip1 a menos que el acceso al DOM sea explícito.
Copia de $(go env GOROOT)/misc/wasm/wasm_exec.js.
Versiona con tu toolchain de Go para que las importaciones permanezcan emparejadas.
Sí. Etiqueta cada archivo que importe syscall/js con js && wasm, o colócalos en un paquete solo para el navegador.
Exporta funciones con js.Global().Set("goDoThing", js.FuncOf(...)) antes de bloquear.
Lee valores de JS a través de args []js.Value.
No en compilaciones js/wasm.
Mantén las pruebas de concurrencia en paquetes linux/amd64 compartidos con la lógica WASM.
Sí, la salida se enruta a la consola del navegador a través del shim del tiempo de ejecución de JS.
Prefiere el registro estructurado para aplicaciones reales.
Usa las utilidades js.CopyBytesToJS y js.CopyBytesToGo para mover datos a través del límite sin copias adicionales siempre que sea posible.
Puedes ejecutar WASM en Web Workers con un cargador wasm_exec.js personalizado, pero las APIs DOM no están disponibles en los workers.
Arquitecta en consecuencia.
TinyGo soporta un subconjunto de objetivos WASM; verifica su documentación de interop JS para tu versión.
Las formas de la API difieren del comportamiento de la toolchain gc.
No. Los hosts WASM headless no proporcionan shims de JS.
Divide los paquetes y haz cumplir con reglas de lint o verificaciones de etiquetas de compilación de go vet.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado Green Tea GC, go fix modernizers - verifica el parche en la compilación), chi (última - verifica en la compilación), gin (última - verifica en la compilación), echo (última - verifica en la compilación), google.golang.org/grpc (última - verifica en la compilación), sigs.k8s.io/controller-runtime (última - verifica en la compilación), kubebuilder (última - verifica en la compilación), tinygo (última - verifica los objetivos de la placa en la compilación), wazero (última - verifica en la compilación), y golangci-lint (última - verifica el conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 16 jul 2026