- WebAssembly ha evolucionado desde su lanzamiento inicial en 2017 con soporte para ejecutar lenguajes de bajo nivel como C/C++, pero aún se le trata como un lenguaje de segunda clase dentro de la plataforma web
- Solo JavaScript puede interactuar directamente con las Web API, y WebAssembly necesita escribir código de enlace en JS (glue code) complejo para lograrlo
- Esta estructura deriva en procedimientos de carga complejos, sobrecarga de rendimiento y rupturas entre toolchains según el lenguaje, lo que empeora la experiencia de desarrollo
- Para resolverlo, Mozilla propone el WebAssembly Component Model, que permite llamar Web API y cargar módulos de forma estandarizada sin JS
- Si este modelo se consolida, WebAssembly podría establecerse como un entorno de ejecución de primera clase dentro del navegador, creando una base que incluso los desarrolladores generales puedan aprovechar con facilidad
Por qué WebAssembly es tratado como un lenguaje de segunda clase
- WebAssembly solo puede acceder a la plataforma web a través de JavaScript y no tiene permiso para llamar Web API directamente
- JavaScript se carga fácilmente con una etiqueta
<script>, pero WebAssembly requiere un proceso de carga manual mediante la API de JS
- Hay que pasar por llamadas API complejas como
WebAssembly.instantiateStreaming(), y el desarrollador debe memorizarlas o automatizarlas con herramientas
- La propuesta esm-integration simplifica la carga al permitir importar archivos
.wasm directamente mediante el sistema de módulos de JS
- Se puede cargar directamente con una forma como
<script type="module" src="/module.wasm"></script>
Restricciones de acceso a Web API
- En JavaScript, algo que puede hacerse con una sola línea como
console.log("hello, world") en WebAssembly exige procedimientos complejos como acceso a memoria de JS, decodificación de cadenas y wrapping de funciones
- Como WebAssembly no puede acceder al objeto
console ni al DOM, debe hacerlo indirectamente desde JS mediante memoria compartida e import/export de funciones
- El código de enlace (glue code) generado en este proceso varía según el lenguaje, y se genera automáticamente con herramientas como
embind o wasm-bindgen
- Sin embargo, esto provoca mayor complejidad de build, sobrecarga en tiempo de ejecución e incompatibilidades entre lenguajes
Razones técnicas por las que WebAssembly no ha podido ser un lenguaje de primera clase
- Dificultad de integración en compiladores: los compiladores de cada lenguaje deben generar por separado código de integración con JS y la plataforma web, y eso no se puede reutilizar
- Incompatibilidad de compiladores estándar: un archivo generado con
rustc --target=wasm no puede ejecutarse directamente en el navegador
- Es necesario instalar aparte un toolchain no oficial que implemente la integración con la plataforma
- Sesgo del ecosistema documental: la documentación web, como MDN, está escrita en su mayoría centrada en JavaScript, lo que eleva la barrera de entrada para usuarios de otros lenguajes
- Problemas de rendimiento: las llamadas al DOM que pasan por enlaces en JS muestran una caída de rendimiento del 45% frente a llamadas directas
- En experimentos con el framework Dodrio, al eliminar el glue de JS el tiempo para aplicar cambios al DOM se redujo a la mitad
- Dependencia de JavaScript: para usar WebAssembly en trabajo real, al final hay que entender JS, lo que lleva al problema de la abstracción con fugas (leaky abstraction)
Aparición del WebAssembly Component Model
- El WebAssembly Component Model define una unidad de ejecución estandarizada que puede usarse en común entre varios lenguajes y runtimes
- Permite acceso a Web API, carga de módulos y linking directamente sin JS
- Puede generarse desde distintos lenguajes y ejecutarse en varios runtimes como el navegador o Wasmtime
- A través de WIT (Interface Description Language) se pueden declarar las API necesarias y llamarlas directamente dentro del componente
- El navegador puede cargar el componente directamente con
<script type="module" src="component.wasm"></script>, y gestionar automáticamente el binding con Web API sin JS
Interoperabilidad con JavaScript
- El Component Model también soporta una estructura de aplicaciones híbridas
- Ejemplo: se puede escribir un decodificador de imágenes en WebAssembly y llamarlo desde JS con una forma como
import { Image } from "image-lib.wasm";
- JS puede usar componentes de WebAssembly importándolos/exportándolos como módulos normales
Perspectivas futuras y participación
- Mozilla está avanzando en la estandarización del Component Model en colaboración con el WebAssembly CG, y Google también se encuentra en etapa de revisión
- Los desarrolladores pueden experimentar en navegador o CLI mediante Jco o Wasmtime
- Si este modelo se consolida, WebAssembly podría expandirse de ser “una función para power users” a una tecnología web utilizable también por desarrolladores generales
4 comentarios
Esto se acerca más a un simple deseo al estilo de Mozilla. El frontend no puede escapar estructuralmente del sistema límbico de la reacción del mercado. En cuanto apareció WebAssembly, se porteó Doom 3. El DOM ya lleva mucho tiempo convertido en un objeto proxy liviano en los navegadores modernos, y considerando el conjunto de instrucciones dedicado a JavaScript en las CPU modernas y los límites cuánticos de un solo núcleo, algo así jamás llegará a tener una ventaja en valor de mercado.
¿Qué sentido tiene un binario de WebAssembly ejecutándose dentro de Electron? Esto solo parece otro GitKraken CLI más, o una cacería de prestigio por portar cosas a Rust.
No sé de lo demás, pero la idea de insertar un módulo wasm como archivo, algo como
<script type="module" src="/module.wasm"></script>, sí me resulta atractiva.Y también hay que decir que las afirmaciones de que la barrera de entrada de WebAssembly es alta son absurdas. Simplemente, la necesidad de hacerlo es menor que la disposición a pagarlo. ¿Quieren rapidez y una huella reducida, pero también usar DOM y CSS? ¿Qué clase de comedia negra es esta?
Opiniones en Hacker News
Es una lástima que no se haya visto como un problema la falta de acceso al DOM cuando se abandonó la meta inicial de dar soporte a WebIDL en WebAssembly y se decidió crear otro IDL
Entiendo la realidad del mercado, claro, pero no puedo dejar de pensar en el tiempo perdido
Enlaces de referencia relacionados: registro del commit, retrospectiva sobre
stringref, artículo de ACMDespués se añadieron dos metas más: una era el soporte para APIs no web, y la otra la interoperabilidad entre lenguajes
WebIDL tiene mucha expresividad porque es la unión de JS y Web API, pero también tenía muchos conceptos que chocaban con esos objetivos
Por eso las interfaces de componentes eligieron un enfoque de intersección, mucho más portable, aunque con menos expresividad
Personalmente creo que el acceso al DOM sí es importante, pero el Wasm CG estaba ocupado con otras prioridades más urgentes
La razón por la que escribí este artículo es que quería decir que todavía recuerdo este problema y que planeo seguir trabajando en él
stringrefregreseEl toolchain y el proceso de build son demasiado complejos, y cada vez que lo uso siento una carga cognitiva considerable
El rendimiento es mucho mejor sin glue, pero también hay más riesgos
Espero que el modelo de componentes no introduzca una nueva capa de complejidad, aunque al ver ejemplos en varios lenguajes ya parece bastante confuso
En particular, si ves el ejemplo en Go, hay demasiados archivos generados, y desde la perspectiva del desarrollador hace mucha falta una simplificación del tooling
Por ahora no parece que se esté eliminando complejidad, sino solo moviéndola de lugar
La especificación de wasm component ha seguido cambiando y ha habido mucho churn
La meta es que un desarrollador web no tenga que escribir WIT directamente y pueda usar la Web API como si fuera una librería
Pero todavía falta mucho camino por recorrer
Por ejemplo, si se dividieran en compartir texto, compartir medios, compartir aplicaciones, etc., también mejoraría la seguridad y equipos pequeños podrían crear alternativas a los navegadores
Pero parece difícil intentar algo así porque el enorme tamaño de las Web API y CSS es uno de los factores que sostienen el monopolio de los navegadores
Estaría bien estandarizar un registry de WebAssembly para poder combinar componentes fácilmente
Al final, la web es el proceso de crear la definición de un sistema operativo distribuido
Está muy bien organizado, desde los conceptos hasta los ejemplos de código
En el ecosistema de JS, los tres proyectos clave son StarlingMonkey, ComponentizeJS y jco
Actualmente el toolchain más maduro es el de Rust, pero el soporte para lenguajes basados en LLVM (C/C++, Go, Python, etc.) también está mejorando poco a poco
La meta de WebAssembly es convertirse en un target de compilación que se integre de forma natural al toolchain local
Si todavía hay que entender glue code específico por lenguaje o dos modelos de runtime, WebAssembly seguirá siendo solo “una herramienta para casos extremos”
Si de verdad se quiere generar un cambio, hay que simplificar la ruta normal de build
No está claro cómo maneja eso el Component Model
El DOM es distinto en cada navegador, y las capacidades cambian en cada carga de página
En una capa de puente con JS es fácil aplicar polyfills, pero en una interfaz WIT es difícil hacer detección de métodos en runtime o usar polyfills
Además del rendimiento, la flexibilidad del ecosistema también importa
Tener que administrar glue code de JS manualmente o depender de herramientas de generación automática se siente como un gran retroceso
En el experimento de Dodrio, omitir el glue logró una reducción del 45% en overhead, lo cual impresiona
Aun así, me da curiosidad cómo se maneja la administración de memoria cuando el WebAssembly Component Model interactúa directamente con la Web API
Quisiera saber si la propuesta de Wasm GC se usa para mantener referencias al DOM, o si todavía depende del GC de JS
Espero que Wasm llegue a establecerse como un verdadero ciudadano de primera clase
sendMessageen v8 y Bun podrían aliviar este problemaPero actualmente el IPC sigue siendo ineficiente, y creo que se necesita algo como una transferencia por páginas de memoria
Dejar que cualquiera ejecute programas complejos en mi computadora era una idea de seguridad totalmente descabellada, pero aun así eso es lo que hemos hecho
Gracias a JS pasamos 20 años sufriendo innumerables bugs de seguridad en navegadores, pero ahora ya existen principios de diseño y mitigaciones bien establecidos
Y aun así, ahora queremos reemplazarlo con otro paradigma de ejecución peligroso, algo irónico pero también hermoso
Los sistemas operativos móviles lo hacen mucho mejor que los de escritorio
Están diseñados con mentalidad de ingeniería y no tienen un flujo de trabajo base amigable para autores
Aun así, da gusto que todavía haya gente preocupándose por estos problemas
Da la impresión de que se está haciendo demasiada ingeniería solo para obtener procesamiento de strings 2x más rápido al mapear el DOM API 1:1
En APIs como WebGL2, WebGPU y WebAudio, el costo del shim de JS ya es mínimo
El problema real está en cosas como la copia de buffers de GPU, y el component model no ayuda ahí
Me gustaría ver un benchmark que pruebe decenas de miles de draw calls en WebGL2 o WebGPU
Además del rendimiento, también es importante mejorar la experiencia del desarrollador (DX)
Ahora mismo es demasiado difícil empezar, y todos tienen que volverse expertos para obtener sus beneficios
Si puede competir con la eficiencia de las apps nativas, sería un cambio con visión de futuro para el futuro de la web
En la era de los agentes de código, la mejora de DX de la que hablan ya no importa tanto