WebAssembly lleva años prometiendo ejecutar código de alto rendimiento directamente en el navegador, sin depender de JavaScript. Kotlin/Wasm es la apuesta de JetBrains para llevar Kotlin a ese mundo: compilar código Kotlin a bytecode WebAssembly y ejecutarlo en el navegador o en entornos serverless. Desde Kotlin 2.0 (mayo de 2024) el soporte es oficial, aunque todavía en fase Alpha en algunos frentes.
Si ya conoces Kotlin y su evolución hacia múltiples plataformas, este artículo te pone al día de qué significa realmente compilar a Wasm, qué ventajas trae WasmGC y cuándo merece la pena dar el salto.
Dos targets, dos propósitos distintos
Kotlin no tiene un único target de WebAssembly, tiene dos, y cubren casos de uso bastante diferentes.
- wasm-js: compila a WebAssembly clásico y puede interoperar directamente con JavaScript en el navegador. Es el target más usado cuando quieres llevar tu lógica Kotlin al navegador.
- wasm-wasi: compila a módulos compatibles con la WebAssembly System Interface, pensados para ejecutarse fuera del navegador: plataformas serverless, runtimes de línea de comandos o entornos de servidor ligeros.
La diferencia con Kotlin/JS clásico es importante: Kotlin/JS transpila el código Kotlin a JavaScript legible (texto plano que V8 o SpiderMonkey JIT-compilan después). Kotlin/Wasm genera bytecode binario que el motor del navegador ejecuta directamente, sin pasar por la capa de transpilación. Menos interpretación, más cercanía al metal.
WasmGC: por qué cambia las cosas
El problema histórico de compilar lenguajes con garbage collector a WebAssembly era el tamaño. WebAssembly clásico no incluye GC, así que cada lenguaje tenía que compilar su propio gestor de memoria dentro del binario .wasm. El resultado: binarios enormes incluso para programas pequeños.
WasmGC (estándar W3C, activo en Chrome 119+ y Firefox 120+) resuelve eso. El navegador gestiona la memoria de los objetos Wasm igual que gestiona la de JavaScript: con su propio garbage collector. Kotlin/Wasm se apoya en WasmGC desde el principio, lo que se traduce en binarios mucho más pequeños que con el GC embebido de la era anterior.
Para los desarrolladores Kotlin esto tiene una consecuencia práctica: el binario que despliegas es compacto y el tiempo de carga en el navegador mejora de forma notable respecto a alternativas que seguían el enfoque antiguo.
Compose Multiplatform para web: el caso más visible
Si hay algo que ha impulsado el interés por Kotlin/Wasm es Compose Multiplatform. La idea es sencilla: el mismo código @Composable que usas para construir la UI de tu app Android funciona en el navegador vía Wasm.
@Composable
fun Saludo(nombre: String) {
Text("Hola, $nombre")
}
Ese componente, sin modificar, puede renderizarse tanto en Android como en un canvas del navegador a través de Kotlin/Wasm. El código de lógica y UI vive en commonMain y cada plataforma solo añade lo específico de su entorno.
En 2026 Compose Multiplatform para web sigue en Alpha. Funciona bien para demos, prototipos y herramientas internas, pero tiene limitaciones reales en interoperabilidad con CSS y en accesibilidad web (ARIA, lectores de pantalla). Para una aplicación web pública con requisitos de accesibilidad estrictos, todavía hay trabajo por delante.
Configurar Kotlin/Wasm en un proyecto KMP
Si tienes un proyecto Kotlin Multiplatform existente, añadir el target wasmJs es cuestión de editar el build.gradle.kts:
kotlin {
wasmJs {
browser()
binaries.executable()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
}
}
val wasmJsMain by getting {
// código específico de la plataforma Wasm-JS
}
}
}
Para añadir también WASI:
@OptIn(ExperimentalWasmDsl::class)
wasmWasi {
binaries.executable()
}
Con esto en su sitio, ./gradlew wasmJsBrowserRun levanta un servidor de desarrollo con live reload. Lo que ves en el navegador es tu código Kotlin ejecutándose como WebAssembly.
Interop con JavaScript desde Kotlin/Wasm
Una de las preguntas habituales al empezar con Kotlin/Wasm es cómo llamar a código JavaScript existente. La respuesta es mediante declaraciones external:
// Declarar una función JS para llamarla desde Kotlin
external fun alert(message: String)
// Importar un módulo JS completo
@JsModule("./miLibreria.js")
external class MiLibreria {
fun procesar(dato: JsString): JsAny
}
La interop en Kotlin/Wasm es más estricta que en Kotlin/JS. Los tipos tienen que ser explícitos y usas tipos de bridge como JsAny, JsString o JsNumber para pasar datos entre el mundo Kotlin y el mundo JavaScript. Más verboso, pero también más predecible: los errores de tipos aparecen en tiempo de compilación, no en producción.
Para exponer código Kotlin hacia JavaScript, la anotación @JsExport marca las clases y funciones que quieres que sean accesibles desde el lado JS:
@JsExport
class Calculadora {
fun sumar(a: Int, b: Int): Int = a + b
}
Wasm-WASI: Kotlin para serverless sin JVM
El target wasm-wasi abre una posibilidad interesante para los que trabajan con funciones serverless. En vez de arrancar una JVM (proceso lento y pesado en arranque en frío), compilas tu código Kotlin a un módulo WASI que plataformas como Fermyon Spin o Wasmer ejecutan directamente.
El resultado es un binario autocontenido, sin dependencias de runtime externas y con un tiempo de inicio prácticamente instantáneo. Para funciones de corta duración que se invoquen miles de veces, esa diferencia de arranque importa.
La limitación actual es que el acceso a librerías del ecosistema JVM queda fuera: si tu función depende de bibliotecas Java legacy, WASI no es la opción. Pero para lógica de negocio pura escrita en Kotlin, es una alternativa seria a considerar en 2026.
Rendimiento frente a Kotlin/JS
La pregunta del millón es si Kotlin/Wasm es más rápido que Kotlin/JS. La respuesta honesta: depende de qué midas.
- Operaciones intensivas de CPU (algoritmos, procesamiento de datos, criptografía): Wasm supera a JS en benchmarks. El compilador AOT de Kotlin/Wasm genera código más predecible que el que los JIT de V8 o SpiderMonkey producen a partir de JS transpilado.
- Manipulación del DOM: JS sigue siendo más natural. Cada llamada al DOM desde Kotlin/Wasm pasa por la capa de interop JS, lo que añade algo de overhead. Para apps con mucha manipulación de DOM, ese coste puede notarse.
- Tamaño inicial: los binarios Wasm con WasmGC son más pequeños que en generaciones anteriores, pero la carga inicial sigue siendo mayor que un bundle JS equivalente en algunos casos.
WasmGC aún no tiene JIT tan agresivo como JavaScript en todos los motores, pero la compilación AOT compensa en muchos escenarios. Con el tiempo, a medida que los motores mejoren su soporte de WasmGC, la brecha a favor de Wasm irá creciendo.
El estado en 2026 y cuándo usarlo
Kotlin/Wasm es Alpha stable en Kotlin 2.x. Eso quiere decir que la API puede cambiar entre versiones menores y que JetBrains no garantiza compatibilidad binaria entre releases. Para proyectos de producción en la web, Kotlin/JS transpilado sigue siendo la opción estable y con más soporte.
Dicho eso, hay escenarios donde ya tiene sentido apostar por Kotlin/Wasm ahora:
- Proyectos KMP nuevos donde quieres web desde el principio y puedes tolerar actualizaciones de API.
- Herramientas internas donde el control de versiones es tuyo y no dependes de usuarios externos.
- Funciones serverless WASI donde el arranque rápido es prioritario.
- Demos y prototipos de Compose Multiplatform para mostrar la visión multiplataforma a stakeholders.
La hoja de ruta de JetBrains es clara: Kotlin/Wasm es el futuro del web en KMP. Pero eso no significa que el target JS vaya a desaparecer pronto. Lo más probable es que ambos convivan durante bastante tiempo, con JS como opción estable de producción y Wasm como target experimental que va madurando.
Si tienes un proyecto KMP donde ya compartes código entre Android, iOS y escritorio, añadir wasmJs como target experimental no cuesta mucho y te permite ir ganando familiaridad con la plataforma antes de que llegue a estable.
Imagen: Pexels / panumas nikhomkhai
