Gracias a WebAssembly podemos ejecutar código de otros lenguajes de programación desde el navegador web con JavaScript. La ventaja de WASM es que, aparte de ser rápido, permite programar en otro lenguaje y aprovechar las librerías presentes en el mismo.
Personalmente he usado Golang con WebAssembly para crear un generador de credenciales, códigos QR y códigos de barras. Todo el procesamiento y generación de imágenes se hace con Go, para luego exportar los resultados a JavaScript.
En este post voy a documentar cómo exportar un arreglo de tipo byte ([]byte) de Go a un Uint8Array de JavaScript, ya que, como te lo dije anteriormente, esto sirve cuando creamos un archivo binario con Go y queremos exportarlo a JavaScript.
Además, un Uint8Array
sí puede ser transportado a través de un WebWorker usando el structured clone algorithm.
Exponer función
Yo expongo la función así:
js.Global().Set("generarCodigos", js.FuncOf(GenerarCodigos))
Aquí Global()
va a devolver el ámbito global desde donde se invoca a la función de WASM.
Si estamos en un WebWorker será self, si estamos en el DOM será window y puede devolver otra cosa según el entorno.
Invocar función que devuelve Uint8Array []byte
Fíjate en que la estoy exponiendo como generarCodigos
así que podré invocarla desde JavaScript con self.generarCodigos()
, y que realmente la función de Go se llama GenerarCodigos
cuyo código dejo a continuación:
func GenerarCodigos(this js.Value, parametros []js.Value) interface{} {
bytesQueConformanElPDF, err := crearPdfConCodigosQr()
if err != nil {
return false
}
destinoBytesPDFEnJS := js.Global().Get("Uint8Array").New(len(bytesQueConformanElPDF))
arreglo := parametros[0]
longitudArreglo := arreglo.Get("length").Int()
log.Printf("El arreglo mide %#v", longitudArreglo)
for indice := 0; indice < longitudArreglo; indice++ {
objeto := arreglo.Index(indice)
nombre := objeto.Get("nombre").String()
existencia := objeto.Get("existencia").Float()
log.Printf("Nombre %#v existencia %#v", nombre, existencia)
}
js.CopyBytesToJS(destinoBytesPDFEnJS, bytesQueConformanElPDF)
return destinoBytesPDFEnJS
}
Realmente el cuerpo de la función no es relevante. Solo estoy probando si estoy recibiendo correctamente el arreglo de objetos desde JavaScript. La parte importante viene a continuación:
Tengo el siguiente arreglo de bytes que representan un PDF:
bytesQueConformanElPDF, err := crearPdfConCodigosQr()
if err != nil {
return false
}
Lo importante es que bytesQueConformanElPDF
es un []byte
.
Copiar arreglo de []byte con js.CopyBytesToJS
Ahora vamos a copiar el arreglo de bytes a JavaScript para devolverlo como resultado de la invocación. Hacemos un nuevo Uint8Array
cuya longitud será la misma que la longitud del arreglo que tenemos en Go y que podemos obtener con len
:
destinoBytesPDFEnJS := js.Global().Get("Uint8Array").New(len(bytesQueConformanElPDF))
Ese fragmento de código es el equivalente a new Uint8Array()
de JavaScript. Luego invocamos a js.CopyBytesToJS
indicando el destino y el origen. El destino será el Uint8Array
que definimos desde Go usando New
, y el origen será el []byte
que tenemos en Go, así:
js.CopyBytesToJS(destinoBytesPDFEnJS, bytesQueConformanElPDF)
Y ahora vamos a tener ese Uint8Array como resultado de la invocación de la función. En mi caso lo tengo con Comlink y un worker expuestos a través de una Store de Vue, así que lo uso así:
const generar = async () => {
const resultado = await dbStore.testing([
{ nombre: "Prueba", existencia: 3 },
{ nombre: "Otra cosa", existencia: 3 },
{ nombre: "Tercer elemento", existencia: 3 },
]);
const blob = new Blob([resultado], { type: "application/pdf" });
const enlace = document.createElement('a');
const url = URL.createObjectURL(blob);
enlace.href = url;
enlace.download = "desde_js.pdf";
enlace.click();
URL.revokeObjectURL(url);
console.log("El resultado es %o", resultado);
}
Aquí, resultado
es el Uint8Array que devolvió la invocación a WebAssembly. En el ejemplo lo estoy descargando pero podemos hacer más cosas.
Conclusión
Así de simple podemos invocar a una función de Go desde JavaScript pasando y recibiendo parámetros complejos, ya que invocamos a la función con un arreglo de objetos que podemos leer en Go, y luego Go nos devuelve un Uint8Array.
Nota importante: aunque aquí uso “Go” y “JavaScript” como si se ejecutaran en entornos separados la verdad es que ambos se van a ejecutar en el navegador web gracias a WebAssembly.