featured image

Después de hablar sobre protocolos como HTTP/2 creo que es momento de centrar el foco en WebAssembly. He estado jugando un poco con Rust y creo que es interesante lo que podemos conseguir, ¿quieres saber cómo funciona y qué es WebAssembly?

Me ha sorprendido encontrar a alguien hablando de WebAssembly y descubrir en su web que no es algo en desarrollo sino una realidad. Un sistema implementado en los principales navegadores (hasta la fecha en Microsoft Edge, Google Chrome, Safari y Mozilla Firefox) y con posibilidad de desarrollar hasta videojuegos en plataformas tan grandes y conocidas como Unity3D.

Pero antes de introducirnos en cómo hacer cosas con WebAssembly vamos a ver un poco su historia...

¿Qué es y de dónde viene WebAssembly?

La motivación de crear un sistema como WebAssembly viene de antiguo. Aún recuerdo los famosos Applets de Java o los elementos ActiveX de Microsoft e incluso mucho código Flash que intentaban dar una solución al problema de rendimiento de JavaScript para actividades tan pesadas como el tratamiento de audio o vídeo, la falta de librerías apropiadas para criptografía o más simple aún, la ofuscación eficiente del código liberado al navegador.

Los intentos por parte de Google para conseguir algo similar pasaron por la creación de elementos PNaCl y NaCl los cuales no tuvieron mucho éxito y fueron desechados y los intentos de Mozilla Firefox con asm.js. Este último asentó las bases para la creación de WebAssembly como paso previo y permitió trabajar a las cuatro compañías implicadas en conseguir este objetivo.

En abril de 2015 se estableció el grupo de trabajo de Wasm en el W3C. La idea acerca de WebAssembly es mantener el sistema lo más agnóstico posible para no limitarlo al entorno web, sino también poder emplearlo en otros ambientes. Quizás la idea de algunos implicados, como Microsoft, pueda darle utilidad para la carga y ejecución de videojuegos en su plataforma XBox, por ejemplo, como ya comienzan a vaticinar algunos medios profetizando con la "muerte" de las app stores.

Finalmente la idea de asm.js culminó en el diseño de WebAssembly y la implementación conjunta en los principales navegadores haciendo posible el desarrollo de aplicaciones en C/C++ y Rust para liberación en los navegadores.

Podríamos preguntarnos, ¿después del intento de Google y Mozilla?, ¿por qué esta vez es diferente? El Doctor Axel Rauschmayer especificó estos elementos diferenciadores y el porqué esta vez esta plataforma sí se quedará con nosotros mucho más tiempo:

  • Primero, es un esfuerzo colaborativo, no de una compañía sola. Hasta ahora, los proyectos han involucrado a Firefox, Chromium, Edge y Webkit.
  • Segundo, la interoperatividad con la plataforma web y JavaScript es excelente. Usar código WebAssembly desde JavaScript será tan simple como importar un módulo.
  • Tercero, esto no va de reemplazar las máquinas de JavaScript, esto va de agregar una nueva característica a ellas. Esto reduce la cantidad de trabajo para implementar WebAssembly y debe ayudar para obtener soporte de la comunidad de desarrollo web.

Por lo que ya sabemos que WebAssembly está disponible en los principales navegadores de Internet a día de hoy, que no viene para reemplazar JavaScript sino para completarlo como ya hicieran otras características como WebRTC o websocket y solo nos queda leer la definición de qué es de la web oficial:

Wasm es un formato binario de instrucciones para una máquina virtual "basada en una pila" (stack-based). Wasm ha sido diseñado para generar objetos portables y compilación desde lenguajes de alto nivel como C/C++/Rust, permitiendo el despliegue en la web para aplicaciones cliente y servidor.

Pero no todo es color es maravilloso, también existen desventajas.

Desventajas de WebAssembly

Las principales desventajas de WebAssembly son la inexistencia de un recolector de basura (garbage collector en inglés o por sus siglas: GC). Esto hace sensiblemente más difícil la gestión sana de una aplicación pudiendo producirse fugas de memoria (leaks) si no controlamos bien nuestro código. No obstante esto es algo que en lenguajes como C/C++ es sabido y controlado con buenas prácticas y herramientas adicionales.

En mi caso opté por Rust y desgraciadamente este lenguaje tiene los mismos problemas cuando el sistema no se programa de forma correcta produciéndose fugas de memoria que acaban agotando los recursos.

Otra desventaja es la compatibilidad. Es decir, los navegadores actuales y principales implementan WebAssembly pero aún quedan navegadores que no lo han implementado y puedes encontrar muchos usuarios empleando versiones de sistemas operativos bastante antiguos con navegadores también muy antiguos que no tendrán soporte tampoco para WebAssembly.

La última desventaja es no poder acceder al DOM. Es decir, no podemos acceder directamente a la información del DOM de JavaScript solo podemos definir funciones para poder emplearlas desde JavaScript. De esta forma JavaScript se mantiene como intermediario y código de cohesión entre el documento web y el código WebAssembly.

Instalando componentes para probar

Voy a seguir un poco las líneas marcadas por Ian J. Sikes en su artículo Get Started with Rust, WebAssembly and Webpack. Juego con ventaja porque ya tengo la mayoría de elementos instalados. Pero básicamente debemos asegurarnos de tener Rust, Node y cmake instalados.

Además, se aconseja tener estas versiones aconsejadas:

  • webpack 3.0.0
  • cargo 0.19.0
  • emcc 1.37.13

Los pasos para compilar la herramienta Emscripten que nos permitirá generar los ficheros .wasm son los siguientes (ten en cuenta que puede tardar hasta 2 horas en completarse la compilación):

cd ~
wget https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz
tar xzf emsdk-portable.tar.gz
cd emsdk-portable
./emsdk update
./emsdk install sdk-incoming-64bit

Después es necesario que agregues a tu PATH algunas rutas. El código provee un script para ayudar con esta tarea por lo que solo tendrás que ejecutar:

source ~/emsdk-portable/emsdk_env.sh

De esta forma tendremos acceso a las herramientas desde cualquier otro directorio. Puedes agregarlo a tu fichero .bashrc para facilitar tenerlo siempre disponible.

Vamos a crear el proyecto:

cargo new tutorial --bin --vcs none
cd tutorial
npm init -y

Es interesante agregar algunos scripts en nuestro proyecto modificando el fichero package.json:

{
  "name": "tutorial",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "author": "",
  "license": "ISC",
  "scripts": {
    "compile": "webpack --progress",
    "serve": "http-server"
  }
}

Tenemos que agregar algunas dependencias más como webpack, http-server y rust-wasm-loader así que vamos a ejecutar el siguiente comando:

npm install --save-dev webpack http-server rust-wasm-loader

Además también necesitaremos instalar una dependencia para Rust:

rustup target add wasm32-unknown-emscripten
cargo install cargo-web

De esta forma ya tenemos agregado el objetivo wasm y agregada la dependencia necesaria para compilar wasm.

Ahora podemos proceder con la creación del resto de ficheros de código. Comenzaremos con el código HTML para el fichero index.html:

<!DOCTYPE html>
<html>
  <head>
    <title>Hello World</title>
  </head>
  <body>
    <div id="container"></div>
    <script src="build/bundle.js"></script>
  </body>
</html>

Además del fichero de configuración para webpack llamado webpack.config.js:

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: __dirname + '/build',
  },
  module: {
    rules: [
      {
        test: /\.rs$/,
        use: {
          loader: 'rust-wasm-loader',
          options: {
            // The path to the webpack output relative to the project root
            path: 'build'
          }
        }
      }
    ]
  },
  // The .wasm 'glue' code generated by Emscripten requires these node builtins,
  // but won't actually use them in a web environment. We tell Webpack to not resolve those
  // require statements since we know we won't need them.
  externals: {
    'fs': true,
    'path': true,
  }
}

Escribimos nuestro fichero de código para Rust con el nombre main.rs con el contenido:

fn main() {
    println!("Hello, world!");
}

// Functions that you wish to access from Javascript
// must be marked as no_mangle
#[no_mangle]
pub fn add(a: i32, b: i32) -> i32 {
    return a + b
}

Por último el código JavaScript que servirá para interactuar con el código Rust (.wasm) lo creamos con el nombre src/index.js y el siguiente contenido:

const wasm = require('./main.rs')

wasm.initialize({noExitRuntime: true}).then(module => {
  // Create a Javascript wrapper around our Rust function
  const add = module.cwrap('add', 'number', ['number', 'number'])

  console.log('Calling rust functions from javascript!')
  console.log(add(1, 2))
})

Compilamos todo y ejecutamos el servidor web en Node.js con los siguientes comandos:

npm run compile
npm run server

Finalmente ejecutamos los siguientes comandos para ejecutar el servidor y seguiremos la siguiente URL para cargar todo en el navegador:

http://localhost:8080/

Desgraciadamente... después de hacerlo todo paso a paso yo al menos he encontrado dos problemas que no me dejan continuar. El primero es que la carga del fichero tutorial.wasm to apunta correctamente a donde se encuentra el fichero. Mientras que bundle.js sí se carga correctamente desde build, la carga de tutorial.wasm se espera desde la raíz y origina un 404 al intentar cargarlo. Solucionando esto a mano:

cp build/tutorial.wasm .

Aún nos queda el último error que creo que es más complejo. El error en la web dice:

Uncaught TypeError: n(...).initialize is not a function (bundle.js:1)

En la consola se puede ver la carga del fichero WebAssembly pero tras este error así que me queda el interrogante si en este tiempo desde que se escribió este artículo WebAssembly haya podido cambiar hasta el punto de tener que emplear el típico código de JavaScript para esperar a que todo se cargue antes de proceder con el código propiamente dicho.

Probé varias cosas pero creo que lo ideal será bajar y emplear el compilador directamente y código JavaScript hecho sin la mediación del plugin (que parece que aún necesita madurar un poco). No obstante hay muchos ejemplos en Internet que se ve que funcionan bastante bien, como por ejemplo...

Otros ejemplos más avanzados

En la web oficial de WebAssembly podemos encontrar un ejemplo bastante vistoso de un videojuego realizado con Unity3D de batalla de tanques. Puedes ver cómo se ve bastante fluido el movimiento y el dibujo de toda la escena gracias a la base de código escrita en WebAssembly que se encarga del tratamiento de las imágenes.

Conclusiones

Me deja un poco mal sabor de boca no poder haber completado el ejemplo de forma funcional. Quizás siguiendo otros ejemplos más sencillos en C o paso a paso en Rust probando poco a poco con pequeños experimentos como el de esta web habría tenido más éxito lo dejo para profundizar un poco más adelante de forma más tranquila y comenzando desde abajo sin tanto código intermedio.

No obstante, considero que estamos en los inicios de esta tecnología y de seguro muchas empresas ya estarán comenzando a sacarle partido para realizar el desarrollo de soluciones que permitan a usuarios de todo tipo conseguir una mejor experiencia de uso de aplicaciones a través de la web, sin necesidad de descargar código adicional y sin requerir tanta CPU o memoria como hasta el momento con el uso de JavaScript.

¿Has tenido ya alguna experiencia con WebAssembly?, ¿Te motiva más usarlo con C, C++ o Rust?, ¿Quieres revisar y saber algo más sobre Rust?, ¿quizás más sobre WebAssembly? ¡Déjanos tu comentario!