DEV Community

Cover image for Perry compila TypeScript a binarios nativos en 10 plataformas sin runtime
lu1tr0n
lu1tr0n

Posted on • Originally published at elsolitario.org

Perry compila TypeScript a binarios nativos en 10 plataformas sin runtime

Perry, un compilador que transforma TypeScript en binarios nativos sin runtime, publicó su versión v0.5.306 con recolección de basura generacional y un nuevo parser de JSON perezoso activado por defecto. La promesa es ambiciosa: tomar el lenguaje que domina el desarrollo web y compilar TypeScript a nativo directamente, para 10 plataformas distintas.

Sin Node.js, sin V8, sin Electron. Para los desarrolladores de LATAM acostumbrados a empaquetar 80 MB de runtime con cada aplicación, la idea de un ejecutable de 2 MB que arranca en un milisegundo merece una mirada de cerca. Eso es exactamente lo que propone Perry TypeScript nativo, y en este artículo desarmamos cómo funciona, qué tan ciertos son sus números y dónde están sus límites reales.

TL;DR

  • Perry v0.5.306 compila TypeScript a binarios nativos sin runtime, usando SWC para parsear y LLVM para generar el código.- Genera ejecutables de 2-5 MB que arrancan en ~1 ms, frente a los ~80 MB y ~30 ms de un proyecto Node.js típico.- Soporta 10 objetivos: macOS, iOS, iPadOS, Android, Linux, Windows, watchOS, tvOS, WebAssembly y Web.- En sus benchmarks contra Node.js v25 reporta hasta 18x de mejora (acumulación: 34 ms vs 617 ms).- Incluye más de 25 widgets nativos vía AppKit, GTK4, Win32, UIKit y JNI, sin web views ni Electron.- La versión v0.5.306 añade recolección de basura generacional y un parser de JSON perezoso activado por defecto.- Ofrece un runtime V8 opcional (15-20 MB) para usar paquetes npm de JavaScript puro.

¿Qué es Perry y por qué compilar TypeScript a nativo importa?

Durante más de una década, ejecutar TypeScript significó una cosa: transpilarlo a JavaScript y correrlo sobre un motor como V8 (Node.js) o JavaScriptCore (Bun, Deno usa V8 también). Ese modelo funciona, pero arrastra un costo permanente. Cada aplicación carga consigo un intérprete completo, un recolector de basura sofisticado y un compilador just-in-time que necesita "calentarse" antes de alcanzar su velocidad máxima. El resultado son binarios de decenas de megabytes y tiempos de arranque medidos en decenas de milisegundos.

Perry rompe con ese modelo. En lugar de interpretar tu código en tiempo de ejecución, lo compila por adelantado (AOT, ahead-of-time) a código de máquina. La diferencia es la misma que existe entre Python y Go, o entre Java y Rust: el trabajo pesado de traducción ocurre una sola vez, en tu máquina o en el servidor de CI, y lo que entregás al usuario es un ejecutable que el sistema operativo corre directamente. La idea de Perry TypeScript nativo no es acelerar el JavaScript: es eliminarlo del producto final.

Esto tiene consecuencias concretas. Un binario sin runtime no tiene dependencias externas que instalar, no necesita una versión específica de Node en la máquina de destino y no sufre el costo de inicialización del motor. Para una herramienta de línea de comandos que se ejecuta cientos de veces al día, o para una aplicación de escritorio que el usuario abre y cierra constantemente, ese milisegundo de arranque frente a 30 marca una diferencia perceptible.
El binario nativo pesa una fracción de un paquete con runtime completo.

Qué anunció la versión v0.5.306

La novedad principal de v0.5.306 es técnica pero significativa: un recolector de basura generacional y un parser de JSON "perezoso" (lazy tape) que ahora viene activado por defecto. Según el proyecto, esta combinación lo hace más rápido que Node.js y Bun en la mayoría de sus benchmarks internos.

El recolector generacional es una técnica clásica de gestión de memoria: parte de la observación de que la mayoría de los objetos mueren jóvenes. En lugar de revisar toda la memoria en cada ciclo, separa los objetos recién creados de los que ya sobrevivieron varios ciclos y dedica el esfuerzo de recolección a la generación joven, que es donde está casi toda la basura. Es el mismo principio que usan los GC modernos de la JVM y de V8, pero aquí aplicado a un binario compilado.

El parser de JSON perezoso es igual de pragmático. En vez de construir todo el árbol de objetos en memoria apenas leés el archivo, Perry mantiene una representación compacta (la "tape") y solo materializa los valores cuando realmente los accedés. Para aplicaciones que leen configuraciones o respuestas de API grandes pero solo usan unos pocos campos, evitar la deserialización completa ahorra tiempo y memoria.

Cómo funciona: SWC para parsear, LLVM para optimizar

La arquitectura de Perry combina dos piezas que ya son estándar de la industria. Para entender el código TypeScript usa SWC, el parser y compilador escrito en Rust que también impulsa herramientas como Next.js y Turbopack. Para generar el código nativo optimizado usa LLVM, la misma infraestructura de compilación detrás de Clang, Rust y Swift. No hay un paso intermedio de JavaScript: el TypeScript se traduce directo a representación intermedia de LLVM, que luego se optimiza y se convierte en código de máquina.

graph LR
  A["main.ts (TypeScript)"] --> B["SWC: parseo y análisis"]
  B --> C["LLVM IR"]
  C --> D["Optimización LLVM"]
  D --> E["Binario nativo (2-5 MB)"]
Enter fullscreen mode Exit fullscreen mode

Apoyarse en SWC y LLVM es una decisión inteligente: en lugar de reinventar un backend de compilación, Perry hereda décadas de trabajo en optimización de código y compatibilidad de plataformas. LLVM ya sabe generar código eficiente para x86-64, ARM64 y WebAssembly, así que cubrir 10 objetivos distintos deja de ser una hazaña imposible. Veamos el caso más simple, un "Hola, mundo" que se compila a un ejecutable nativo:

// hola.ts
const saludo: string = "Hola, mundo!";
console.log(saludo);

// Se compila a un binario nativo de ~2 MB.
// No necesita runtime.
Enter fullscreen mode Exit fullscreen mode

Perry también trae una biblioteca estándar con implementaciones nativas de APIs familiares de Node como fs, path, crypto, os, Buffer y child_process. Eso significa que mucho código que ya escribiste para Node puede recompilarse con cambios mínimos:

// leer.ts
import { readFileSync } from "fs";

const contenido = readFileSync("datos.json", "utf-8");
const datos = JSON.parse(contenido);
console.log(`Registros encontrados: ${datos.length}`);
Enter fullscreen mode Exit fullscreen mode

Los números: hasta 18x más rápido que Node.js

El proyecto publica una batería de benchmarks comparando Perry v0.5.279 contra Node.js v25, medidos como la mediana de 11 corridas en una Apple M1 Max. Conviene leerlos con cabeza fría —son benchmarks del propio proyecto, sobre cargas sintéticas— pero la magnitud es coherente con lo que se espera al eliminar el intérprete:

  • Acumulación — 18x más rápido: 34 ms frente a 617 ms.- Creación de objetos — 11x: 1 ms frente a 11 ms.- JSON roundtrip — 5.3x: 75 ms frente a 394 ms.- Overhead de bucles — 4.5x: 12 ms frente a 54 ms.- Cálculo matemático intensivo — 3.6x: 14 ms frente a 51 ms.- Lectura de arreglos — 3.3x: 4 ms frente a 13 ms.- Fibonacci — 3.2x: 318 ms frente a 1022 ms.

Más allá del rendimiento de cómputo, las cifras estructurales son las que cuentan la historia real. El tamaño del binario baja de los ~80 MB de un proyecto Node y los ~90 MB de uno con Bun a apenas 2-5 MB con Perry. El tiempo de arranque cae de ~30 ms (Node) y ~10 ms (Bun) a cerca de 1 ms. Y las dependencias de runtime pasan de "Node.js completo" a "ninguna".

💭 Clave: Las mejoras más grandes aparecen en bucles cerrados y manipulación de objetos, justo donde el JIT de V8 paga el costo de calentamiento. En cargas dominadas por I/O la diferencia se reduce, porque ahí el cuello de botella no es el lenguaje sino el sistema operativo.
Las mayores ventajas aparecen en cómputo puro, no en I/O.

Diez plataformas y widgets nativos de verdad

El aspecto más diferenciador de Perry no es el rendimiento del CLI, sino su apuesta por la interfaz gráfica. El compilador no genera web views ni empaqueta un navegador: produce widgets nativos reales de cada plataforma. En macOS usa AppKit, en Linux GTK4, en Windows Win32, en iOS y iPadOS UIKit, y en Android se apoya en JNI para hablar con las Views del sistema. watchOS y tvOS pasan por SwiftUI. A esto suma WebAssembly y la Web (compilando a JavaScript) como destinos adicionales, para 10 objetivos en total marcados como estables.

Esto significa que un botón en tu código TypeScript se convierte en un NSButton real en macOS y en un widget GTK en Linux, con la apariencia, accesibilidad y comportamiento que el usuario espera de su sistema operativo. Perry ofrece más de 25 widgets: botones, campos de texto, áreas de texto, tablas, canvas, vistas con scroll, códigos QR, campos seguros y pantallas de carga, entre otros. El proyecto cita aplicaciones reales construidas con este enfoque, como un visor GUI nativo de MongoDB de ~7 MB con arranque en frío de menos de un segundo, o un editor de código con terminal, Git y LSP integrados.

Otras características interesantes para quienes vienen del mundo Node: un sistema de plugins en tiempo de compilación (los módulos se componen al construir, sin overhead de IPC), multithreading real con hilos del sistema operativo —incluyendo parallelMap y parallelFilter, con seguridad verificada en compilación que rechaza capturas mutables— e internacionalización en tiempo de compilación con reglas de plural CLDR para más de 30 locales.

Perry frente a React Native, Flutter y compañía

La tabla comparativa que publica el proyecto se posiciona directamente contra los grandes del desarrollo multiplataforma. React Native interpreta JavaScript en tiempo de ejecución (Hermes o V8) y se comunica con la UI a través de un bridge. Flutter compila AOT pero usa su propio motor de renderizado (Skia) y el lenguaje Dart, así que no dibuja widgets del sistema sino los suyos propios. Kotlin Multiplatform con Compose, .NET MAUI y NativeScript ofrecen distintos grados de nativo, pero todos cargan algún runtime: Skia, Mono/.NET o un motor de JavaScript.

La diferencia que Perry reclama es ser el único que marca las tres casillas a la vez: código compilado a binario nativo, widgets reales de plataforma y cero overhead de runtime. Es una afirmación fuerte, y conviene ser escéptico hasta ver proyectos grandes en producción fuera del propio ecosistema. Pero la propuesta técnica es coherente: si compilás a nativo y usás los toolkits del sistema, efectivamente no hay un runtime intermedio que cargar.

📌 Nota: "Sin runtime" no significa "sin gestión de memoria". Perry incluye su propio recolector de basura generacional dentro del binario. La diferencia con Node es que ese GC está compilado y especializado, no es un motor de propósito general de decenas de megabytes.

Cómo empezar

El flujo básico gira en torno al comando perry compile (o perry build), que toma tu archivo de entrada y produce el ejecutable. La invocación es la misma en cada sistema; solo cambia cómo ejecutás el binario resultante. En macOS y Linux:

$ perry compile main.ts
Compiling main.ts...
✓ Compiled executable: main (2.3 MB)
$ ./main
Hola, mundo!
Enter fullscreen mode Exit fullscreen mode

En Windows el binario sale con extensión .exe:

> perry compile main.ts
> .\main.exe
Hola, mundo!
Enter fullscreen mode Exit fullscreen mode

Para llevar la app a producción, Perry suma comandos de empaquetado y firma: perry publish arma el paquete, lo notariza y lo envía a la App Store o Play Store, mientras que perry verify (impulsado por la herramienta Geisterhand) corre pruebas de UI automatizadas en las distintas plataformas. Para usar paquetes npm de JavaScript puro existe un runtime V8 opcional que activás con un flag, a costa de subir el binario a unos 15-20 MB. Las instrucciones de instalación exactas y los binarios para cada sistema están en el repositorio oficial y en la documentación del proyecto.

💡 Tip: Si tu objetivo es una herramienta CLI o un servicio pequeño que arranca y termina rápido, ahí es donde el modelo sin runtime brilla más. Para apps que dependen mucho del ecosistema npm con paquetes nativos de Node, evaluá primero qué tanto cubre la biblioteca estándar de Perry antes de migrar.

Limitaciones y qué sigue

Conviene tener los pies en la tierra. Perry está en la serie 0.5.x, es decir, antes de una versión 1.0 estable, y eso implica APIs que pueden cambiar. Su mayor desafío estructural es el ecosistema: el valor histórico de Node y npm no es el lenguaje, sino el millón largo de paquetes disponibles. Muchos de esos paquetes usan APIs nativas de Node o addons en C++ que no se compilan trivialmente. El runtime V8 opcional es un puente pragmático, pero usarlo reintroduce parte del peso que Perry promete eliminar.

También hay que validar las afirmaciones de "estable" en cada plataforma con proyectos reales y de tamaño considerable. Compilar un "Hola, mundo" a 10 objetivos es una cosa; mantener paridad de comportamiento de UI compleja a través de AppKit, GTK4, Win32, UIKit y JNI es un problema notoriamente difícil que han enfrentado todos los frameworks multiplataforma. La automatización de pruebas con Geisterhand apunta justamente a ese riesgo.

⚠️ Ojo: Antes de comprometer un proyecto serio, probá tu caso de uso real: integraciones con npm, los widgets que necesitás y la plataforma objetivo concreta. Una herramienta en 0.5.x es ideal para experimentar y para CLIs internas, pero merece cautela en productos críticos.

El rumbo del proyecto es claro: acercar el desarrollo de aplicaciones nativas a quienes ya saben TypeScript, cubriendo todo el ciclo desde la compilación hasta la publicación en tiendas. Si logra resolver la brecha del ecosistema npm y madurar hacia un 1.0, Perry podría convertirse en una alternativa genuina para equipos que hoy eligen entre el peso de Electron y la curva de aprendizaje de Swift, Kotlin o Dart.

📖 Resumen en Telegram: Ver resumen

Preguntas frecuentes

¿Perry reemplaza a Node.js?

No directamente. Perry compila TypeScript a binarios nativos sin runtime, lo que lo hace ideal para CLIs y aplicaciones de escritorio o móviles. Node.js sigue siendo más conveniente cuando dependés intensamente del ecosistema npm con paquetes que usan APIs nativas. Para esos casos, Perry ofrece un runtime V8 opcional como puente.

¿Qué significa que no tenga runtime?

Significa que el ejecutable final no necesita Node.js, V8 ni ningún intérprete instalado para correr. El código TypeScript se traduce por adelantado a código de máquina con LLVM. Perry sí incluye su propio recolector de basura compilado dentro del binario, pero no carga un motor de JavaScript completo.

¿Realmente es hasta 18x más rápido que Node.js?

Esa cifra (acumulación: 34 ms vs 617 ms) proviene de los benchmarks del propio proyecto sobre cargas sintéticas en una Apple M1 Max. Es coherente con eliminar el costo de calentamiento del JIT, pero conviene medir tu propio caso de uso. En tareas dominadas por I/O la ventaja se reduce significativamente.

¿En qué plataformas funciona Perry?

En 10 objetivos marcados como estables: macOS, iOS, iPadOS, Android, Linux, Windows, watchOS, tvOS, WebAssembly y la Web. Usa los toolkits nativos de cada sistema (AppKit, GTK4, Win32, UIKit, SwiftUI y JNI) en lugar de web views.

¿Puedo usar paquetes de npm con Perry?

Para código JavaScript puro, sí, activando el runtime V8 opcional, lo que sube el binario a unos 15-20 MB. Para paquetes que dependen de APIs nativas de Node o de addons en C++, la compatibilidad depende de lo que cubra la biblioteca estándar nativa de Perry, que incluye implementaciones de fs, path, crypto, os, Buffer y child_process.

¿Está listo para producción?

El proyecto cita aplicaciones reales en producción, pero la numeración 0.5.x indica que aún no alcanza una versión 1.0 estable, con APIs que pueden cambiar. Es recomendable validar tu caso concreto —plataformas, widgets e integraciones— antes de comprometer un proyecto crítico.

Referencias

  • Perry — sitio oficial — anuncio, benchmarks y comparativa de frameworks.- PerryTS/perry en GitHub — repositorio del compilador y guía de instalación.- Documentación oficial de Perry — referencia de APIs e internals del compilador.- SWC — parser y compilador de TypeScript en Rust que usa Perry.- LLVM — infraestructura de compilación detrás de la generación de código nativo.- React Native — alternativa basada en runtime usada en la comparación.- Flutter — framework AOT con renderizador propio (Skia) y lenguaje Dart.

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Top comments (0)