Acabo de publicar un post sobre las ventajas y usos de WebAssembly en donde prometí que traería un ejemplo con Go.
De hecho ya tenía el ejemplo desde antes, pero no iba a poner toda la definición de WebAssembly aquí.
En fin, veamos cómo utilizar Go para compilar a WebAssembly y llamar al código desde JavaScript.
Pero no te confundas, WebAssembly no es JavaScript, es código que se ejecuta directamente en el navegador, solo que JavaScript le dice al navegador cómo usarlo.
Go y WebAssembly
El proceso es sencillo una vez que se entiende. Al principio hasta yo me compliqué pero he tratado de juntar toda la información que recabé y ponerla de forma entendible.
Si al avanzar por el código te pierdes un poco o no sabes la relación de los archivos puedes visitar siempre el repositorio en Github.
También puedes probar el ejemplo básico y el que hace una petición http.
Recuerda que la carga es lenta, pues el archivo wasm del segundo ejemplo pesa 8 MB.
Todo el código generado no será JavaScript ni Go, sino Web Assembly.
Preparando el entorno
Necesitas instalar y configurar Go, además de tener la última versión instalada.
Antes de compilar necesitas establecer las variables de GOOS en js y GOARCH en wasm. En Windows es:
set GOOS=js
set GOARCH=wasm
Ejecuta eso en la terminal y ya quedó. Si te lo preguntas, GOOS es Go Operating System y GOARCH es Go Architecture.
Por cierto, al compilar se hace así:
go build -o archivo.wasm archivo.go
Es decir, se debe generar un archivo wasm, no uno exe o lo que sea según tu SO.
Finalmente cabe mencionar que debemos poner este comentario al inicio de nuestros archivos que queremos compilar únicamente para WebAssembly:
// +build js,wasm
Eso es para que al compilar solo sean tomados en cuenta si las variables del sistema están establecidas como lo indico arriba.
Copiar archivo de JavaScript
El pegamento entre WebAssembly de Go y JavaScript es proporcionado por el mismo Go cuando lo descargamos. Ese archivo JavaScript vamos a cargarlo desde nuestra app web.
Su ubicación es:
GOROOT/misc/wasm/wasm_exec.js
En donde GOROOT es la raíz de Go. Si estás en Windows puedes ir a la carpeta pegando en la barra del explorador de archivos lo siguiente:
%GOROOT%misc\wasm
Y copiando el archivo wasm_exec.js
Nota: en mi caso la carpeta es C:\Program Files\Go\misc\wasm
, pero es cuestión de que busques la ruta de instalación de Go.
Es importante que utilices el que Go proporciona para tu plataforma, no intentes descargarlo desde otra página.
Hola WebAssembly: generar wasm
Ahora crea un archivo de Go como si fuera un simple hola mundo:
// +build js,wasm
/*
Un acercamiento a WebAssembly con Go
@author parzibyte
Visita: parzibyte.me/blog
*/
package main
import (
"log"
)
func main() {
log.Printf("Hola WebAssembly. 5 + 5 = %d", 5+5)
}
Si te fijas, no hay código extra de JavaScript, es Go puro.
Ahora establecemos las variables y compilamos con:
go build -o main.wasm main.go
Eso habrá generado un archivo wasm.
Hola WebAssembly: utilizar y cargar Wasm
Para cargar el archivo de WebAssembly necesitamos una página web. Crea un archivo HTML así:
<!DOCTYPE html>
<html lang="es">
<!--
Un acercamiento a WebAssembly con Go
@author parzibyte
Visita: parzibyte.me/blog
-->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>WebAssemby en Go | By Parzibyte</title>
</head>
<body>
<h1>WebAssembly en Go</h1>
<a href="//parzibyte.me/blog">By Parzibyte</a>
<!-- Cargar los scripts de WASM--->
<script src="./wasm_exec.js"></script>
<script>
const go = new Go();
console.log("Cargando el archivo main.wasm");
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject)
.then((result) => {
console.log("Carga completa");
go.run(result.instance);
});
</script>
</body>
</html>
No estamos haciendo nada más que cargar un script y dentro del script usar fetch para traer el archivo wasm.
Ahora copia los archivos main.wasm, wasm_exec.js e index.html a un directorio público, es decir, accesible desde la web.
Puedes crear un servidor simple con Python o Go, así como usar la carpeta pública de XAMPP o LAMP. Incluso podrías usar un hosting gratuito.
Nota: si usas Apache (XAMPP o LAMP) por favor mira este tutorial, pues Apache no conoce los archivos wasm y no los sirve con el MIME adecuado. Esto no tiene nada que ver con WebAssembly, sino con Apache.
No importa lo que hagas siempre y cuando los archivos queden servidos a través de http.
Después de eso visita la web en donde los hayas alojado. yo los puse en htdocs/wasm_basico
y se ve así (recuerda abrir la consola con F12):
Wow, una suma en Go. ¿No eso también se puede hacer con JavaScript? claro que sí, pero recuerda que es un ejemplo básico y hemos logrado compilar Go a WebAssembly de manera nativa.
Puedes probar el ejemplo aquí, verás que no hay código JavaScript que imprima, es código de WebAssembly.
Ejemplo avanzado de WebAssembly con Go
Arriba vimos un hola mundo pero realmente no hicimos algo que demostrara cómo usar Go o la diferencia entre éste y JavaScript.
Ahora vamos a ver cómo interactuar con el DOM y usar el paquete net/http de Go para hacer una petición GET como la que vimos en este post.
El código de Go queda así:
// +build js,wasm
/*
Petición HTTP GET con WebAssembly
¿XMLHttpRequest o fetch? no, mejor WebAssembly con Go
@author parzibyte
Visita: parzibyte.me/blog
*/
package main
import (
"io/ioutil"
"log"
"net/http"
"syscall/js"
)
// Una simple función que hace una petición GET HTTP
// para demostrar cómo podemos usar net/http de GO
// en el navegador web. Sí, has leído bien
// Mira: https://parzibyte.me/blog/2019/05/21/peticion-post-get-put-delete-go-net-http/
func peticionHttp() (string, error) {
clienteHttp := &http.Client{}
// Si quieres agregar parámetros a la URL simplemente haz una
// concatenación :)
url := "https://httpbin.org/get"
peticion, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
// Podemos agregar encabezados
peticion.Header.Add("Content-Type", "application/json")
peticion.Header.Add("X-Hola-Mundo", "Ejemplo")
respuesta, err := clienteHttp.Do(peticion)
if err != nil {
// Maneja el error de acuerdo a tu situación
return "", err
}
// No olvides cerrar el cuerpo al terminar
defer respuesta.Body.Close()
cuerpoRespuesta, err := ioutil.ReadAll(respuesta.Body)
if err != nil {
return "", err
}
log.Printf("Código de respuesta: %d", respuesta.StatusCode)
log.Printf("Encabezados: '%q'", respuesta.Header)
contentType := respuesta.Header.Get("Content-Type")
log.Printf("El tipo de contenido: '%s'", contentType)
// Aquí puedes decodificar la respuesta si es un JSON, o convertirla a cadena
respuestaString := string(cuerpoRespuesta)
log.Printf("Cuerpo de respuesta del servidor: '%s'", respuestaString)
return respuestaString, nil
}
func main() {
// Declaración de elementos del DOM
// Equivalente a document.querySelector("#hacerPeticion")
botonHacerPeticion := js.Global().Get("document").Call("querySelector", "#hacerPeticion")
// Equivalente a document.querySelector("#resultado")
resultado := js.Global().Get("document").Call("querySelector", "#resultado")
// Un canal para eso del código asíncrono y las rutinas que envuelve las llamadas HTTP
canal := make(chan struct{})
// botonHacerPeticion.addEventListener("click", () => {})
botonHacerPeticion.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
resultado.Set("innerHTML", "Cargando...")
log.Println("Haciendo petición")
go func() {
respuestaString, err := peticionHttp()
log.Printf("El error: '%v'", err)
if err != nil {
resultado.Set("innerHTML", "Error haciendo petición")
}
// resultado.innerHTML = respuestaString;
resultado.Set("innerHTML", respuestaString)
}()
return nil
}))
<-canal
}
Ya estamos interactuando con elementos del DOM y agregando listeners. No te confundas con los canales, son cosa de otro post, basta con que sepas que es para que la petición HTTP sea válida y asíncrona.
Para compilar ejecuta lo mismo:
go build -o main.wasm main.go
El código JavaScript y HTML se queda intacto. Puedes probarlo aquí.
Al ejecutarlo, su funcionamiento es el siguiente:
No necesitamos pelearnos para hacer una petición HTTP asíncrona ni utilizar AJAX, ni elegir entre XMLHttpRequest, fetch, $.ajax o axios.
Conclusión
Fueron ejemplos sencillos pero demostraron cómo podemos usar las librerías y paquetes de Go para hacer nuestras aplicaciones web más portables y rápidas.
Por cierto, mucha información presente aquí fue tomada de la wiki oficial. La documentación del paquete js de syscall está aquí.
Eso es todo lo que necesitas, ahora ve y crea algo maravilloso con WebAssembly.
Pingback: Migrando un sitio de WordPress a otro servidor - Parzibyte's blog