Mover un proyecto Node.js a Bun: aspectos internos del entorno de ejecución, Lambda y migración
Entienda qué reemplaza Bun en una cadena de herramientas de Node.js, por qué arranca rápidamente, cómo ejecuta TypeScript y en AWS Lambda, y cómo migrar un proyecto paso a paso.
Fuentes: Everything You Need to Know About Bun: A Beginner’s Guide, Migration, and Architecture
Un servicio típico de Node.js depende de cuatro o cinco herramientas separadas antes de poder ejecutar siquiera una línea de lógica de negocio: un gestor de paquetes, un ejecutor de TypeScript, un marco de pruebas y un bundler. Bun reduce toda esa estructura a un único archivo ejecutable que funciona como entorno de ejecución, gestor de paquetes, bundler y ejecutor de pruebas, con el objetivo de ser un reemplazo directo de Node.js. Esta guía explica qué es lo que realmente sustituye Bun, de dónde proviene su velocidad, cómo maneja TypeScript y el despliegue serverless, y cómo migrar un proyecto existente sin sorpresas. Para una comparación más amplia basada en pruebas de rendimiento que también incluye a Deno, consulte Node.js, Deno y Bun comparados.
Qué sustituye Bun en una cadena de herramientas típica
Ejecutar JavaScript fuera del navegador requiere un entorno de ejecución, y durante más de una década ese ha sido Node.js. Sin embargo, un proyecto moderno con Node suele reunir varios herramientas independientes alrededor de él:
- npm o Yarn para instalar y gestionar paquetes;
tsc,ts-nodeotsxpara compilar o ejecutar TypeScript;- Jest o Vitest para pruebas unitarias;
- esbuild, webpack o Rollup para crear paquetes listos para producción.
Bun incluye todas estas funcionalidades en un único binario. Al instalarlo se obtiene un entorno de ejecución, un gestor de paquetes, un compilador de paquetes, un ejecutor de pruebas y la posibilidad de ejecutar TypeScript directamente, sin necesidad de configuración adicional. La verdadera ventaja radica en eliminar los elementos intermedios: menos archivos de configuración y menos incompatibilidades entre las herramientas.
Analizando los pros y contras frente a Node.js
La decisión se reduce principalmente a la velocidad e integración por un lado, y a la madurez del ecosistema por el otro.
Dónde destaca Bun
- Una cadena de herramientas integrada. No hay un ejecutor de pruebas, un bundler ni un transpilador de TypeScript separados que configurar.
- Instalaciones rápidas. Según las mediciones propias de Bun,
bun installes hasta 30 veces más rápido quenpm; los resultados dependerán del proyecto y del estado de la caché. - Arranques rápidos desde cero. Los procesos se inician en milisegundos, lo cual es crucial en entornos serverless donde la latencia de arranque se cobra y afecta a los usuarios.
- TypeScript sin configuración previa. Los archivos
.tsy.tsxse ejecutan directamente, sin necesidad de pasos de compilación ni scripts de carga.
.env se cargan automáticamente sin necesidad de dotenv, y el entorno de ejecución incluye clientes integrados para SQLite, Postgres, S3 y Redis.Dónde Node.js sigue siendo superior
- Madurez. Node.js cuenta con más de 15 años de historia en entornos de producción y versiones oficiales de soporte a largo plazo en las que las empresas basan sus planes. Bun es más reciente y aún está cerrando brechas de compatibilidad en casos extremos.
- Complementos nativos. La gran mayoría de los paquetes de npm (a menudo se menciona que son más del 95%) funcionan sin cambios, pero los paquetes antiguos que compilan código en C++ mediante
node-gyppueden seguir presentando problemas.
Por qué Bun arranca y funciona rápidamente
No se trata simplemente de “Zig supera a C++”
Bun está escrito en Zig, y es tentador atribuir su velocidad al lenguaje. En la práctica, el Zig compilado y el C++ compilado funcionan a aproximadamente la misma velocidad en la CPU. La diferencia proviene del diseño: Node.js cuenta con muchas abstracciones acumuladas a lo largo de los años, mientras que Bun reimplementó la entrada/salida de archivos, el manejo de cadenas y las redes desde cero, con un fuerte enfoque en evitar asignaciones de memoria innecesarias.
Un motor de JavaScript diferente
Node.js funciona con V8, el motor que Google desarrolló para Chrome. Bun utiliza JavaScriptCore, el motor que Apple creó para Safari. JavaScriptCore está optimizado para un inicio rápido y una agresiva gestión de la memoria, lo que ayuda a explicar las cifras de tiempo de inicio en frío de Bun. Cada motor tiene tareas en las que es más rápido, por lo que los servicios de larga duración y con alto consumo computacional deben someterse a pruebas de rendimiento en lugar de asumir automáticamente que mejorarán.
Las ventajas no se limitan a macOS
Aunque JavaScriptCore surgió de Safari, tanto él como Bun se compilan de forma nativa para Linux en x86_64 y ARM64, así como para Windows. Las características de rendimiento se mantienen en los servidores en la nube habituales, independientemente del sistema operativo.
Ejecutar Bun en AWS Lambda
Bun puede servir como backend HTTP a través de su API nativa Bun.serve() o mediante frameworks conocidos como Express, Fastify y Hono. En AWS Lambda, sus rápidos arranques en frío pueden reducir tanto la latencia como el costo de ejecución. Lambda no ofrece un entorno de ejecución gestionado de Bun para seleccionar en la consola, por lo que los equipos suelen elegir uno de estos tres enfoques:
- Capa de entorno de ejecución personalizada. Publicar el binario
buncomo una capa de Lambda y ejecutarlo sobre la imagen Linux baseprovided.al2023. - Imagen de contenedor. Construir la función a partir de la imagen Docker oficial
oven/bun, subirla a Amazon ECR y desplegarla como un contenedor. - Compilar con Bun, ejecutar en Node. Utilizar
bun buildpara empaquetar el código en un único archivo.js, y luego ejecutar ese archivo en el entorno de ejecución gestionado de Node.js de Lambda.
La tercera opción es la más conservadora: obtienes el rápido empaquetado de Bun mientras mantienes el entorno de ejecución soportado por AWS en producción. Renuncias a las API específicas del entorno de ejecución de Bun y a su ventaja de arranque en frío, por lo que es adecuada para equipos que desean adoptar Bun de forma gradual.
Cómo ejecuta Bun TypeScript
Puedes ejecutar archivos directamente con el comando bun, ya sean de JavaScript o TypeScript: bun index.js, bun index.ts o bun --watch index.ts para reiniciar automáticamente cuando cambien los archivos.
Cuando ejecutas un archivo .ts, el transpilador de Bun elimina las anotaciones de tipo al instante y lo hace completamente en memoria. No escribe una carpeta dist/, un directorio .build/ ni archivos temporales .js en el disco.
La advertencia importante es que eliminar los tipos no es lo mismo que verificarlos. Para mantener una inicialización instantánea, Bun no realiza verificación de tipos estáticos en tiempo de ejecución, por lo que el código con errores de tipo seguirá funcionando. La verificación de tipos se realiza en tu editor, como VS Code o Cursor, y en los procesos CI, donde puedes ejecutar bunx tsc --noEmit. Considera ese paso en CI como obligatorio.
Migrar un proyecto Node.js existente paso a paso
Dado que Bun sigue las API de Node y las convenciones de npm, un proyecto sencillo suele migrarse con solo unos pocos cambios. Hazlo en una rama y ejecuta toda tu suite de pruebas al final.
Paso 1: Eliminar módulos antiguos y archivos de bloqueo
Comience con un árbol de dependencias limpio para que Bun resuelva todo por sí mismo: elimine la carpeta node_modules junto con el archivo de bloqueo que utilice el proyecto, ya sea package-lock.json, yarn.lock o pnpm-lock.yaml.
Al eliminar el archivo de bloqueo antiguo, las versiones se vuelven a determinar a partir de los rangos indicados en package.json, por lo que es posible obtener versiones más recientes que las anteriores. Si es importante lograr una reproducibilidad exacta, verifique si su versión de Bun puede migrar un archivo de bloqueo existente en su lugar y revise la diferencia entre las versiones resueltas.
Paso 2: Instalar con Bun
Ejecute bun install. Bun lee su package.json existente, descarga los paquetes y crea un archivo bun.lock, el cual debe guardar en los commits.
Paso 3: Actualizar las instrucciones de ejecución en package.json
Sustituya las referencias a node, ts-node o tsx en la sección scripts:
- start:
"bun src/index.js"reemplaza a"node src/index.js"; - dev:
"bun --watch src/index.ts"reemplaza a"ts-node-dev src/index.ts"; - test:
"bun test"reemplaza a"jest"o"vitest".
El ejecutor de pruebas es en gran medida compatible con la API de Jest, pero las pruebas que dependen en gran medida del mockeo o configuración específicos de Jest podrían requerir ajustes.
Paso 4: Cambiar las definiciones de TypeScript
En los proyectos de TypeScript, sustituya las definiciones de tipo de Node por las de Bun ejecutando bun remove @types/node seguido de bun add -d @types/bun.
Luego, actualice la entrada types en compilerOptions de tsconfig.json. Las guías antiguas mencionan allí "bun-types"; con el paquete actual @types/bun la entrada suele ser "bun", así que consulte la documentación de TypeScript de Bun para la versión que haya instalado.
Paso 5: Sustituir módulos que no son compatibles
Los módulos estándar de Node como node:fs, node:path y node:http funcionan sin cambios. Los paquetes que dependen de enlaces nativos en C++ suelen ser la causa de problemas, y varios tienen sustitutos sencillos:
- sustituya
bcryptporbcryptjso@node-rs/bcrypt; - sustituya
sqlite3porbun:sqlite, que viene incluido en Bun;
dotenv, ya que Bun carga automáticamente el archivo .env.Tenga cuidado con ese último cambio. Si el mismo código debe seguir ejecutándose en Node, por ejemplo en la configuración de Lambda solo para compilación mencionada anteriormente, mantenga dotenv o cargue la configuración de forma explícita para que el comportamiento no difiera entre los entornos de ejecución.
Puntos clave
- El principal valor de Bun es la consolidación: un único binario reemplaza al gestor de paquetes, al ejecutor de TypeScript, al marco de pruebas y al bundler.
- Su velocidad se debe a una reimplementación que tiene en cuenta la asignación de memoria y al rápido inicio de JavaScriptCore, no a que Zig sea intrínsecamente más rápido que C++.
- Bun elimina los tipos de TypeScript en memoria pero nunca los verifica, por lo que mantenga
tsc --noEmiten los procesos de integración continua. - En Lambda puede elegir entre una capa personalizada, una imagen de contenedor o empacar con Bun y ejecutarlo en un Node.js gestionado.
- La migración es en su mayor parte mecánica, pero hay que prestar atención a la nueva resolución de los archivos lockfile, a los complementos nativos y a cualquier comportamiento que dependa del cargado automático de
.env.
Lecturas relacionadas
- Componentes integrados de Bun 1.4 que pueden reemplazar sharp, Puppeteer, node-pty y más — Un recorrido práctico por las funciones integradas de Bun 1.4 para manejar imágenes, navegadores, Markdown, cron, terminal, scripts en paralelo y pruebas, además de cómo probarlas de forma segura en proyectos reales.
- Coordinar llamadas a herramientas de LLM en Node.js con Promise.withResolvers() — Vea cómo Promise.withResolvers() simplifica la orquestación de llamadas a herramientas en un Lambda de Node.js que utiliza Claude en Bedrock, así como los tiempos de espera, las reintentos y los límites que no cubre.