WebAssembly en Go: tutorial y ejemplos

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.

WASM y Go – Hola mundo

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.

1 – Ubicación de wasm_exec

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

2 – Establecer variables y compilar a WASM con 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):

3 – Hola WebAssembly

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:

WebAssembly en Go usando net http para petición HTTP

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.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

1 comentario en “WebAssembly en Go: tutorial y ejemplos”

  1. Pingback: Migrando un sitio de WordPress a otro servidor - Parzibyte's blog

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *