WebAssembly es de las tecnologías con el mayor potencial del último tiempo, ya que nos permite insertar en páginas web cosas que previamente no podíamos hacer solamente con Javascript o HTML.
En el siguiente post vamos a ver paso a paso cómo construir un componente capaz de detectar el lenguaje en el que está escrito un texto mediante NLP (Natural Language Processing), todo sin tener que recurrir a ninguna API y por tanto, capaz de funcionar de manera offline. El resultado final se puede ver en este demo.
Para entender qué es WebAssembly, primero debemos entender que es Assembly. Assembly es cualquier lenguaje de bajo nivel que tenga una fuerte correspondencia con el código de máquina, y por tanto, depende generalmente de la arquitectura en la cual ejecutemos el código (ver diferencia entre x86 y ARM).
Para ser un poco más precisos, cuando compilamos códigos en lenguajes tales como C, lo que realmente estamos haciendo es (a grandes rasgos) crear un binario con el código en Assembly (por esto mismo no podemos compartir binarios entre x86 y ARM). Si estás interesado en ver cómo luce el código en x86 Assembly, te recomiendo el siguiente link.
WebAssembly, es la contraparte web de Assembly. Es un formato binario diseñado para ejecutarse en navegadores de forma portable, segura, eficiente y rápida. Al ser un formato binario, se debe escribir en otro lenguaje para que el compilado sea WebAssembly, tal como con Assembly normal. Los lenguajes más comunes a usar son C/C++, Rust o Go, ya que permiten funcionalidades más completas que un lenguaje como Javascript. En esta ocasión utilizaremos Rust, ya que es un lenguaje moderno de bajo nivel.
Si bien los fundamentos de Rust se escapan de los objetivos de este post, para los que quieran aprender más, aparte de la documentación oficial, recomiendo este canal de Youtube.
Para seguir este tutorial debes tener instalado las siguientes herramientas, disponibles para Windows, MacOS y Linux.
La elección de Next.js como framework para React se debe a que es uno de los más completos y modernos, proporcionando muchas herramientas como server-side rendering o una mejor estructura de carpetas.
Podemos partir un proyecto en Next.js sin necesidad de tener instalado el CLI, gracias a npx.
npx create-next-app --example with-chakra-ui-typescript with-chakra-ui-typescript-app
O usar el template de Drimo, el cual viene con features como auth gates, Typescript y otros que son muy útiles a la hora de hacer una app real.
git clone [email protected]:rokket-labs/nextjs-template.git
Usaremos la segunda opción, ya que nos da la facilidad de volver este proyecto de experimentación en un proyecto real de forma muy rápida.
Vamos a la carpeta del proyecto e instalamos las dependencias.
cd nextjs-template
yarn install
Para correr el proyecto en modo development ejecutaremos yarn dev
y veremos algo como esto:
Si vamos a nuestro IDE preferido, podemos cambiar esta página en src/pages/index.tsx
.
Aún dentro de nuestro proyecto de Next.js, vamos a ir a src/
y crearemos lo que será el código que se compilará a WebAssembly (desde ahora me referiré a él simplemente como WASM).
cd src
cargo new detect-language
cargo run
Ahora tendremos que decirle a Rust qué tipo de librería será nuestro proyecto y agregar una dependencia. Para esto agregaremos las siguientes líneas a Cargo.toml
:
Notaremos que ahora Cargo.toml
nos dará un error. Esto se debe a que nuestro proyecto tiene un main.rs
pero en Cargo.toml
dijimos que es una librería, por tanto está buscando el archivo lib.rs
(para mayor explicación, ver este video). Creamos lib.rs
dentro de src/
, ya sea con el IDE o mediante la consola.
cd src
touch lib.rs
Con el siguiente snippet, podremos llamar la función alert de Javascript dentro de Rust, a modo de hello world:
Antes de seguir deberemos instalar wasm-pack para poder compilar a WASM. Una vez instalado corremos:
wasm-pack build
Creará una carpeta llamada pkg/
donde se encontrarán todos los archivos necesarios para importar nuestro módulo WASM. Podemos ver que incluso crea un archivo de definición de types, el cual nos resultará sumamente útil para utilizar con Typescript.
Una vez creado el módulo de WASM, podemos usar el método dynamic que exporta Next.js, el cual nos ayuda a usar dynamic imports. Si no estás usando Next.js, loadable-components hace la misma función. Editamos src/pages/index.tsx
, agregando las siguientes líneas:
Como podemos notar, tenemos types sin ningún esfuerzo adicional 🔥.
Ahora usaremos nuestro RustComponent en alguna parte de la página, en mi caso lo pondré justo abajo del Hero:
Al clickear el botón veremos lo siguiente:
Para detectar en qué lenguaje está escrito un texto usaremos el package whatlang, el cual está basado en el siguiente paper. Agregamos whatlang a las dependencias, por lo que la lista debería verse así:
Reemplazamos el ejemplo que teníamos en lib.rs
por lo siguiente:
Volvemos a hacer wasm-pack build
y actualizamos el componente en React. Para esto agregaremos un Textarea (Chakra UI) y pasaremos el value a RustComponent:
También actualizamos RustComponent para que acepte text como prop:
También actualizamos RustComponent para que acepte text como prop. ¡Ya detecta el lenguaje!
Ahora, si borramos el texto, nos da un error que no ayuda mucho:
Por suerte, la gente de rustwasm creó un package para tener errores más descriptivos, llamado console__error_panic_hook. Lo agregamos a Cargo.toml
y lo usamos en lib.rs
:
Ahora sabemos que el error se debe a que unwrap asume que la función no dará error, pero cuando no puede detectar que lenguaje es (porque simplemente no tiene texto), nos da un error del cual no se puede recuperar (panicked).
Existen muchas formas de error handling en Rust, pero la que usaremos en este tutorial es similar a try catch en Javascript. El keyword match detecta si una expresión tuvo error o no, en caso de no tenerlo, extraemos el lenguaje y en caso de tenerlo, retornaremos un texto como fallback. Como ya no tendremos error, podemos eliminar el console error panic hook:
Nuestra app ya no crashea y lo mejor de todo es que el binario de WASM ¡solo pesa 279 KB!
Mediante esta prueba de concepto, se puede ver reflejado el nivel de madurez al que ha llegado WebAssembly y su integración, en este caso, con React.
Ejemplos como lo son Google Earth o Figma nos demuestran que se puede utilizar a nivel productivo, dándonos así una manera de crear códigos seguros, eficientes y agnósticos a la arquitectura del procesador.
Esta interoperabilidad ha dado paso a proyectos como Wasmer (Run any code on any client) o Polkadot (A scalable, interoperable & secure network protocol for the next web).
Finalmente, solo queda agregar que WebAssembly nos abre las puertas a una enorme cantidad de tipos de proyectos que antes eran simplemente impensables, y las aplicaciones que se le pueden dar son infinitas.
En Drimo, creemos que esta tecnología puede dar un paso importante en cómo utilizamos herramientas como el machine learning, inteligencia artificial y manipulación de objetos 3D junto a aplicaciones web con una gran interfaz.