WebAssembly fuera del navegador: WASI 2.0, el Component Model y por qué Wasm lo va a cambiar todo
Para la mayoría de los desarrolladores web, WebAssembly significa una cosa: hacer cálculos pesados más rápido en el navegador. Lo habrás visto en Figma, Photoshop web o algún editor de video que funciona entero en una pestaña. Mola, pero de nicho.
Ahora bien, hay algo que quizás se te pasó: WebAssembly se está convirtiendo en un runtime universal. Ya no solo para navegadores—para servidores, funciones en el edge, dispositivos IoT, e incluso como alternativa a contenedores Docker en ciertos escenarios.
WASI 0.3.0 acaba de salir. El Component Model se está estabilizando. Cloudflare Workers, Fastly Compute y Fermyon Cloud ejecutan Wasm en producción a escala. Docker tiene soporte nativo para Wasm. Y las herramientas por fin cruzaron el umbral de "realmente usable".
No es el futuro. Está pasando ahora, y rápido.
A ver, ¿qué es WASI y por qué debería importarme?
La versión corta
WASI (WebAssembly System Interface) le da a los módulos Wasm la capacidad de acceder a recursos del sistema—archivos, red, reloj, números aleatorios—de forma segura y portable. Piensa en ello como el POSIX de Wasm.
Por qué importa
Sin WASI, WebAssembly solo puede ejecutar cómputo puro. Puede procesar números, pero no puede leer un archivo, hacer una petición HTTP ni siquiera saber la hora actual. Para el navegador, donde JavaScript maneja todo el I/O, no hay problema. Pero para el lado del servidor, es inútil.
WASI resuelve esto con interfaces estandarizadas:
Sin WASI: Con WASI:
┌──────────────┐ ┌──────────────┐
│ Módulo Wasm │ │ Módulo Wasm │
│ │ │ │
│ Solo cómputo│ │ Archivos │
│ puro │ │ Red │
│ │ │ Timers │
│ Sin I/O │ │ Variables │
│ Sin archivos│ │ HTTP │
└──────────────┘ └──────────────┘
El modelo de seguridad: esto te va a gustar
Aquí es donde WASI se pone realmente bueno. Los procesos tradicionales del SO tienen acceso a todo por defecto. WASI es justo al revés: un módulo Wasm solo puede acceder a lo que tú le des explícitamente:
# Ejecutar un módulo Wasm con wasmtime # Este módulo SOLO puede leer de /data y escribir en /output wasmtime run --dir /data::readonly --dir /output myapp.wasm # No puede: # ❌ Acceder a /etc/passwd # ❌ Hacer peticiones de red (no se le dio --tcplisten) # ❌ Leer variables de entorno (no se le dio --env) # ❌ Acceder a ningún otro directorio
Compará eso con un contenedor Docker, que por defecto tiene acceso a todo su sistema de archivos. WASI viene cerrado de fábrica. Seguridad por defecto, no por configuración.
WASI 0.3.0: qué cambió
I/O asíncrono
El cambio más grande. Las versiones anteriores de WASI solo soportaban I/O bloqueante, lo que significaba que un módulo Wasm no podía manejar múltiples operaciones concurrentemente. WASI 0.3.0 introduce un modelo de futures y streams:
// WASI 0.3.0 - handler HTTP asíncrono use wasi::http::incoming_handler; use wasi::io::streams; async fn handle_request(request: IncomingRequest) -> OutgoingResponse { // Lectura de archivo no bloqueante let data = streams::read("config.json").await?; // Llamada HTTP no bloqueante let api_response = wasi::http::outgoing_handler::handle( OutgoingRequest::new("https://api.example.com/data") ).await?; // Construir respuesta OutgoingResponse::new(200, api_response.body()) }
Esto es crítico. Sin I/O asíncrono, Wasm no tenía chance contra Node.js o Go en cargas de red. Ahora sí.
Interfaces estabilizadas
| Interfaz | Funcionalidad | Estado |
|---|---|---|
wasi:filesystem | Lectura/escritura de archivos | Estable |
wasi:sockets | Networking TCP/UDP | Estable |
wasi:http | Cliente y servidor HTTP | Estable |
wasi:clocks | Reloj y timers | Estable |
wasi:random | Aleatoriedad criptográfica | Estable |
wasi:cli | Argumentos, env vars, stdio | Estable |
wasi:io | Streams y futures asíncronos | Nuevo en 0.3 |
Lo que todavía falta (siendo honestos)
- Acceso a GPU: No hay interfaz GPU estandarizada. Entrenamiento de IA y gráficos siguen fuera del alcance.
- Threading: La propuesta de threads para WASI existe pero no está en 0.3.0. Hay concurrencia asíncrona, pero no paralelismo real.
- Acceso al DOM: WASI es para entornos fuera del navegador. El Wasm del navegador sigue hablando con el DOM a través de JavaScript.
Component Model: acá está el cambio de verdad
WASI le da capacidades de I/O a Wasm. El Component Model le da composabilidad. Y acá es donde la cosa se pone seria.
El dolor que resuelve
¿Querés usar una librería de procesamiento de imágenes escrita en Rust desde una app en Go? Hoy tenés tres opciones, y ninguna es buena:
- Reescribirla en Go (mucho trabajo)
- Usar CGo con bindings de C (doloroso)
- Llamarla como servicio separado por HTTP (lento, complejo)
El Component Model agrega una cuarta: compilar ambos a componentes Wasm y enlazarlos directamente. Comparten memoria, se llaman mutuamente, corren en el mismo proceso. Sin importar el lenguaje.
Enfoque tradicional:
┌──────────┐ HTTP ┌──────────┐
│ App Go │ ←────────→ │ Servicio │
│ │ red │ Rust │
└──────────┘ overhead └──────────┘
Enfoque Component Model:
┌────────────────────────────────┐
│ Componente Wasm compuesto │
│ ┌──────────┐ ┌──────────┐ │
│ │ Go │←→│ Rust │ │
│ │ lógica │ │ librería│ │
│ └──────────┘ └──────────┘ │
│ Llamadas directas a funciones │
│ Memoria compartida, zero overhead│
└────────────────────────────────┘
WIT: El lenguaje de interfaces
Los componentes se comunican a través de definiciones WIT (Wasm Interface Type). Piensa en protobuf, pero para componentes Wasm:
// image-processor.wit package mycompany:[email protected]; interface process { record image { width: u32, height: u32, data: list<u8>, } record options { quality: u8, format: string, } resize: func(img: image, target-width: u32, target-height: u32) -> image; compress: func(img: image, opts: options) -> list<u8>; detect-faces: func(img: image) -> list<bounding-box>; record bounding-box { x: u32, y: u32, width: u32, height: u32, } } world image-service { export process; }
Defines este WIT una vez, y luego:
- Lo implementas en Rust, Go, Python, JavaScript, C/C++, o C#
- Lo consumes desde cualquiera de esos lenguajes
- Sin FFI, sin serialización, sin código pegamento
Construyamos un componente
Vamos a crear un componente en Rust y usarlo desde JavaScript:
Paso 1: Definir la interfaz
// greeter.wit package example:[email protected]; interface greet { greet: func(name: string) -> string; } world greeter { export greet; }
Paso 2: Implementar en Rust
// src/lib.rs wit_bindgen::generate!("greeter"); struct MyGreeter; impl Guest for MyGreeter { fn greet(name: String) -> String { format!("Hello, {}! Welcome to the Component Model.", name) } } export!(MyGreeter);
# Cargo.toml [package] name = "greeter" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] wit-bindgen = "0.36"
Paso 3: Compilar el componente
# Compilar el módulo Wasm cargo build --target wasm32-wasip2 --release # El target wasm32-wasip2 produce directamente un componente # target/wasm32-wasip2/release/greeter.wasm
Paso 4: Consumir desde JavaScript (con jco)
# Instalar jco (JavaScript Component Tools) npm install -g @bytecodealliance/jco # Transpilar el componente Wasm a un módulo importable en JS jco transpile greeter.wasm -o greeter-js/
// app.js import { greet } from './greeter-js/greeter.js'; console.log(greet("World")); // "Hello, World! Welcome to the Component Model."
Una función Rust llamada directamente desde JavaScript. Zero serialización, zero HTTP, type-safe.
Dónde se está usando Wasm en producción (en serio)
1. Edge computing: Cloudflare Workers y Fastly Compute
El caso de uso más maduro. Tanto Cloudflare Workers como Fastly Compute ejecutan Wasm nativamente:
// Cloudflare Worker usando Wasm // La computación pesada en Wasm, el routing en JS import { process_image } from './image-processor.wasm'; export default { async fetch(request) { const imageData = await request.arrayBuffer(); // Esto corre a velocidad casi nativa const result = process_image( new Uint8Array(imageData), 800, // ancho objetivo 600 // alto objetivo ); return new Response(result, { headers: { 'Content-Type': 'image/webp' }, }); }, };
¿Por qué Wasm en el edge?
- Cold start: Contenedores tardan cientos de ms → Wasm tarda microsegundos
- Memoria: Node.js ~50-200MB → Wasm 1-10MB
- Seguridad: Sandbox por defecto, escape de contenedores imposible
- Portabilidad: El mismo binario corre en x86, ARM, RISC-V
2. Sistemas de plugins: extensiones seguras
Un caso de uso que está creciendo rápidamente. Ejecutar plugins de usuarios en el mismo proceso es un riesgo de seguridad; en contenedores separados es lento. Ejecutarlos como componentes Wasm resuelve ambos problemas:
Arquitectura de plugins tradicional:
┌─────────────────────────┐
│ Aplicación Host │
│ ┌───────────────────┐ │
│ │ Plugin (JS/Lua) │ │ ← Acceso total a memoria del host
│ │ Puede crashear │ │ ← Acceso al filesystem del host
│ │ Memory leaks │ │ ← Syscalls arbitrarios
│ └───────────────────┘ │
└─────────────────────────┘
Arquitectura de plugins Wasm:
┌─────────────────────────┐
│ Aplicación Host │
│ ┌───────────────────┐ │
│ │ Plugin (Wasm) │ │ ← Sandbox de memoria aislado
│ │ No puede crashear│ │ ← Solo capabilities otorgadas
│ │ Memory-safe │ │ ← Uso de recursos determinista
│ └───────────────────┘ │
└─────────────────────────┘
Ejemplos reales:
- Envoy Proxy: plugins Wasm para routing y filtrado personalizado
- Shopify Functions: lógica de merchants ejecutada como Wasm
- Figma: plugins corren en sandbox Wasm
- Zed Editor: extensiones son componentes Wasm
- Extism: framework para sistemas de plugins basados en Wasm
3. Docker + Wasm: alternativa a contenedores
Docker incorporó soporte nativo para Wasm desde Docker Desktop 4.15+. Puedes ejecutar contenedores Wasm junto a contenedores Linux tradicionales:
# Dockerfile tradicional FROM node:20-slim WORKDIR /app COPY . . RUN npm install CMD ["node", "server.js"] # Tamaño de imagen: ~200MB # Cold start: ~500ms
# "Dockerfile" Wasm (base wasm/wasi) FROM scratch COPY myapp.wasm /myapp.wasm ENTRYPOINT ["/myapp.wasm"] # Tamaño de imagen: ~2MB # Cold start: ~1ms
# Ejecutar un contenedor Wasm docker run --runtime=io.containerd.wasmtime.v2 \ --platform wasi/wasm \ myregistry/myapp:latest
Cuándo usar contenedores Wasm:
- ✅ Endpoints de API sin estado
- ✅ Pipelines de procesamiento de datos
- ✅ Herramientas CLI y utilidades
- ✅ Funciones que necesitan cold start rápido
- ❌ Aplicaciones que necesitan GPU
- ❌ Servicios stateful de larga duración
- ❌ Apps con muchas dependencias a nivel de SO
4. Inferencia de IA en el edge
Ejecutar modelos ML ligeros en Wasm ya es viable:
// Inferencia ONNX en Wasm use wasi_nn::{Graph, GraphEncoding, ExecutionTarget, Tensor}; fn classify_image(image_data: &[u8]) -> Vec<f32> { // Cargar el modelo ONNX let graph = Graph::load( &[model_bytes], GraphEncoding::Onnx, ExecutionTarget::Cpu, ).unwrap(); let context = graph.init_execution_context().unwrap(); // Configurar tensor de entrada context.set_input(0, Tensor::new( &[1, 3, 224, 224], // batch, channels, height, width TensorType::F32, image_data, )).unwrap(); // Ejecutar inferencia context.compute().unwrap(); // Obtener salida context.get_output(0).unwrap() }
Especialmente atractivo para IA en el edge: el mismo binario Wasm corre en Cloudflare Workers, en un navegador o en un dispositivo IoT.
Rendimiento: menos palabras, más números
Tiempo de arranque
Comparación de cold start (handler HTTP simple):
┌──────────────────────────────────────────────┐
│ Docker (Node.js): ~500ms │
│ Docker (Go): ~100ms │
│ AWS Lambda (Node): ~200ms │
│ Wasm (Wasmtime): ~1ms │
│ Wasm (Spin): ~1ms │
└──────────────────────────────────────────────┘
Esta mejora de 100-500x es la razón por la que las plataformas serverless adoran Wasm. Para workloads scale-to-zero, el tiempo de arranque impacta directamente la latencia.
Throughput
Para trabajo CPU-bound, Wasm alcanza el 80-95% de velocidad nativa. La brecha se ha reducido mucho:
Benchmark de JSON parsing (1MB payload):
┌──────────────────────────────────────────────┐
│ Rust nativo: 12.3ms │
│ Wasm (Wasmtime): 14.1ms (87% nativo) │
│ Node.js: 28.7ms │
│ Python: 89.4ms │
└──────────────────────────────────────────────┘
Memoria
Footprint de memoria (servidor HTTP idle):
┌──────────────────────────────────────────────┐
│ Node.js: ~50MB │
│ Go: ~15MB │
│ Wasm (Spin): ~2MB │
└──────────────────────────────────────────────┘
Donde Wasm NO es más rápido
Siendo honestos sobre las limitaciones:
- Workloads I/O-intensivos: La capa de abstracción de WASI agrega overhead para operaciones de archivo/red
- Cómputo sostenido: Código nativo con SIMD y threading sigue siendo más rápido para procesamiento prolongado
- GPU: Sin acceso GPU vía WASI, entrenamiento de IA y gráficos quedan fuera
Manos a la obra: tu primera app WASI
Usando Spin (por Fermyon)
La forma más rápida de empezar con Wasm del lado del servidor:
# Instalar Spin curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash # Crear proyecto spin new -t http-rust my-api cd my-api
El código generado:
// src/lib.rs use spin_sdk::http::{IntoResponse, Request, Response}; use spin_sdk::http_component; #[http_component] fn handle_request(req: Request) -> anyhow::Result<impl IntoResponse> { let path = req.uri().path(); match path { "/" => Ok(Response::builder() .status(200) .header("content-type", "application/json") .body(r#"{"status": "ok", "runtime": "wasm"}"#)?), "/heavy-compute" => { // Esto corre a velocidad casi nativa let result = fibonacci(40); Ok(Response::builder() .status(200) .body(format!(r#"{{"fib40": {}}}"#, result))?) } _ => Ok(Response::builder() .status(404) .body("Not found")?), } } fn fibonacci(n: u64) -> u64 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2), } }
# spin.toml spin_manifest_version = 2 [application] name = "my-api" version = "0.1.0" [[trigger.http]] route = "/..." component = "my-api" [component.my-api] source = "target/wasm32-wasip2/release/my_api.wasm" [component.my-api.build] command = "cargo build --target wasm32-wasip2 --release"
# Compilar y ejecutar localmente spin build spin up # Probar curl http://localhost:3000/ # {"status": "ok", "runtime": "wasm"} # Deploy a Fermyon Cloud (gratis) spin deploy
Usando wasmtime directamente
Para más control:
# Instalar wasmtime curl https://wasmtime.dev/install.sh -sSf | bash # Compilar tu app Rust con target WASI rustup target add wasm32-wasip2 cargo build --target wasm32-wasip2 --release # Ejecutar wasmtime run target/wasm32-wasip2/release/myapp.wasm
El ecosistema de herramientas en 2026
Runtimes
| Runtime | Enfoque | Producción | Notas |
|---|---|---|---|
| Wasmtime | Propósito general | ✅ | Bytecode Alliance, el más maduro |
| Wasmer | Universal | ✅ | Registry, WAPM, gestor de paquetes |
| WasmEdge | Edge/AI | ✅ | Proyecto CNCF, soporte ONNX |
| Spin | Serverless | ✅ | Fermyon, mejor DX |
| Wazero | Embedding en Go | ✅ | Pure Go, sin CGo |
Soporte por lenguaje
No todos los lenguajes están igualmente preparados para desarrollo WASI:
| Lenguaje | Component Model | WASI 0.3 | Madurez |
|---|---|---|---|
| Rust | ✅ Completo | ✅ | Producción |
| Go (TinyGo) | ✅ | ✅ | Producción |
| Python (componentize-py) | ✅ | ⚠️ Parcial | Beta |
| JavaScript (ComponentizeJS) | ✅ | ⚠️ Parcial | Beta |
| C/C++ | ✅ | ✅ | Producción |
| C#/.NET | ✅ (experimental) | ⚠️ | Alpha |
Herramientas clave
# wasm-tools: La navaja suiza de Wasm cargo install wasm-tools # Inspeccionar un componente wasm-tools component wit myapp.wasm # Componer componentes wasm-tools compose main.wasm --adapt adapter.wasm -o composed.wasm # jco: JavaScript Component Tools npm install -g @bytecodealliance/jco # Transpilar componente Wasm a JS jco transpile component.wasm -o output/ # cargo-component: Compilar componentes Rust cargo install cargo-component cargo component new my-component cargo component build
Component Model en la práctica: ¿cómo se ve esto en un servicio real?
Un ejemplo de arquitectura real usando componentes Wasm:
┌─────────────────────────────────────────────────────────┐
│ API Gateway │
│ (Componente Wasm) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Auth │ │ Rate Limiter │ │ Logger │ │
│ │ (Rust) │ │ (Go) │ │ (Python) │ │
│ │ Componente │ │ Componente │ │ Componente │ │
│ └──────┬──────┘ └──────┬───────┘ └──────┬───────┘ │
│ └────────────────┼──────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Lógica de Negocio │ │
│ │ (Cualquier lenguaje) │ │
│ │ ┌──────────────────┐ │ │
│ │ │ Procesador de │ │ │
│ │ │ Imágenes (Rust) │ │ │
│ │ └──────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Cada componente:
- Escrito en el mejor lenguaje para su tarea
- Sandboxed con solo los permisos necesarios
- Composable con otros componentes vía interfaces WIT
- Hot-swappable sin reiniciar el host
Errores típicos (que vas a querer evitar)
1. "Solo compilo mi app Node.js a Wasm"
No funciona así. Las APIs de Node.js no existen en WASI. Necesitas frameworks diseñados para ello como Spin o WasmCloud.
2. Esperar compatibilidad POSIX completa
WASI no es POSIX. Es una interfaz nueva y mínima. fork(), memoria compartida, señales... nada de eso existe. Diseña tus aplicaciones para el modelo WASI, no intentes portar apps Linux directamente.
3. Tamaño del binario
Los binarios Wasm pueden ser grandes si no optimizas:
# Rust: optimizado para tamaño [profile.release] opt-level = "s" # Optimizar para tamaño lto = true # Link-time optimization strip = true # Eliminar info de debug codegen-units = 1 # Mejor optimización # Resultado: ~5MB → ~500KB
4. No todos los lenguajes son iguales
Rust y C/C++ generan el mejor Wasm. Go (vía TinyGo) funciona bien pero tiene limitaciones. Python y JavaScript pasan por intérpretes compilados a Wasm, con el overhead que eso implica.
Lo que viene
Ahora (Q1 2026)
- ✅ WASI 0.3.0 publicado (I/O asíncrono)
- ✅ Component Model estabilizándose
- ✅ Docker Wasm en producción
- ✅ Cloudflare, Fastly, Fermyon operando a escala
Q2-Q3 2026
- Propuesta de threads para WASI avanzando
- Registros de componentes (gestión de paquetes para componentes)
- Más soporte de lenguajes (componentes Java, Swift, Kotlin)
wasi-nnestabilizándose para inferencia AI
H2 2026 - 2027
- WASI 1.0 release estable esperado
- Propuesta de acceso a GPU
- Adopción más amplia de la industria más allá del edge computing
- Potencial integración con scheduling nativo de Kubernetes
La visión final
El objetivo es un mundo donde:
- Escribes código en cualquier lenguaje
- Lo compilas a un binario universal (componente Wasm)
- Lo ejecutas en cualquier lugar: navegador, servidor, edge, IoT, embebido
- Lo compones con otros componentes sin importar su lenguaje
- Con seguridad por defecto: sandbox, basado en capabilities, sin autoridad implícita
Solomon Hykes (cofundador de Docker): "Si WASM+WASI hubiera existido en 2008, no habríamos necesitado crear Docker. Así de importante es."
Conclusión
WebAssembly fuera del navegador ya no es una promesa—es realidad. Con casos de uso que ya funcionan:
- Edge computing: Cold starts 100-500x más rápidos que contenedores
- Plugins: Sandbox seguro, plugins multi-lenguaje
- Serverless: Arranque en microsegundos, footprint en megabytes
- Composición: Mezclar lenguajes en una sola aplicación vía Components
Qué hacer:
- Hoy: Instala
wasmtimey ejecuta un hello world WASI (5 minutos) - Esta semana: Prueba Spin para un endpoint HTTP simple. Deploy gratis a Fermyon Cloud
- Este mes: Evalúa si alguno de tus microservicios o edge functions se beneficiaría del cold start y la memoria de Wasm
- Este trimestre: Explora el Component Model. Construye un sistema de plugins o compón componentes de distintos lenguajes
El navegador fue solo el comienzo. Wasm se está comiendo la infraestructura desde el edge hacia adentro. El Component Model es la pieza faltante que lo transforma de una tecnología interesante en un cambio de plataforma.
El sandbox se abrió de par en par.
Explora herramientas relacionadas
Prueba estas herramientas gratuitas de Pockit