Volver al blog

Bun 1.2: El Runtime JavaScript Que Está Rompiendo Récords de Performance

Hola HaWkers, si acompañas el ecosistema JavaScript, probablemente ya oíste hablar de Bun. Este runtime relativamente nuevo está conquistando desarrolladores con números de performance impresionantes.

¿Ya te frustraste esperando que npm install termine? ¿O tal vez sentiste que tu servidor Node.js podría ser más rápido? Bun promete resolver esos problemas y mucho más.

Qué Es Bun

Bun es un runtime JavaScript y toolkit todo-en-uno que incluye:

Componentes Principales:

  • Runtime JavaScript (alternativa a Node.js)
  • Package manager (alternativa a npm/yarn/pnpm)
  • Bundler (alternativa a webpack/esbuild)
  • Test runner (alternativa a Jest/Vitest)
  • TypeScript transpiler nativo

¿El diferencial? Todo esto fue escrito en Zig y C++ para máxima performance, usando JavaScriptCore (motor de Safari) en vez de V8.

Novedades de Bun 1.2

Windows Nativo

Finalmente, soporte completo a Windows:

# Instalación en Windows (PowerShell)
powershell -c "irm bun.sh/install.ps1 | iex"

# O vía npm
npm install -g bun

# Verificar instalación
bun --version
# 1.2.0

Antes de la versión 1.2, Bun en Windows dependía de WSL. Ahora funciona nativamente.

S3 Object Storage Nativo

Bun 1.2 introdujo APIs nativas para Amazon S3:

// Acceso nativo a S3 - sin SDKs pesadas
import { S3Client } from "bun";

const s3 = new S3Client({
  accessKeyId: Bun.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: Bun.env.AWS_SECRET_ACCESS_KEY,
  region: "us-east-1"
});

// Upload de archivo
const file = Bun.file("./image.png");
await s3.write("my-bucket/image.png", file);

// Download
const downloaded = await s3.file("my-bucket/image.png");
await Bun.write("./downloaded.png", downloaded);

// Streaming de archivos grandes
const stream = s3.file("my-bucket/large-video.mp4").stream();

Postgres Nativo

Driver PostgreSQL integrado al runtime:

// Sin instalar pg o postgres.js
import { SQL } from "bun";

const sql = new SQL({
  hostname: "localhost",
  database: "myapp",
  username: "user",
  password: "password"
});

// Queries con tagged templates
const users = await sql`SELECT * FROM users WHERE active = true`;

// Queries parametrizadas (seguras contra SQL injection)
const userId = 123;
const user = await sql`SELECT * FROM users WHERE id = ${userId}`;

// Transacciones
await sql.begin(async (tx) => {
  await tx`UPDATE accounts SET balance = balance - 100 WHERE id = 1`;
  await tx`UPDATE accounts SET balance = balance + 100 WHERE id = 2`;
});

Performance Comparada

Package Manager

Vamos a comparar instalación de dependencias:

# Proyecto con ~50 dependencias

# npm
time npm install
# real: 45.2s

# yarn
time yarn install
# real: 32.1s

# pnpm
time pnpm install
# real: 18.7s

# bun
time bun install
# real: 2.3s

Bun es consistentemente 10-20x más rápido que npm.

Runtime Performance

Servidor HTTP:

// Bun native server
Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello World");
  }
});

// Resultados: ~185,000 req/s

Comparativo de requisiciones por segundo:

Runtime Hello World JSON API File Serve
Bun 1.2 185,000 142,000 78,000
Deno 2.0 125,000 95,000 45,000
Node 22 118,000 88,000 52,000

Bundler

# Build de proyecto React grande (~500 componentes)

# webpack
time npx webpack build
# real: 28.4s

# esbuild
time npx esbuild src/index.tsx --bundle --outdir=dist
# real: 0.8s

# bun
time bun build src/index.tsx --outdir=dist
# real: 0.4s

Usando Bun en la Práctica

Creando un Proyecto

# Crear proyecto
mkdir mi-proyecto && cd mi-proyecto
bun init

# Estructura creada:
# mi-proyecto/
#   .gitignore
#   bun.lockb
#   index.ts
#   package.json
#   README.md
#   tsconfig.json

Servidor Web Completo

// server.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";

const app = new Hono();

// Middlewares
app.use("*", logger());
app.use("/api/*", cors());

// Database connection (nativo de Bun)
const db = new SQL({
  hostname: Bun.env.DB_HOST,
  database: Bun.env.DB_NAME,
  username: Bun.env.DB_USER,
  password: Bun.env.DB_PASSWORD
});

// Routes
app.get("/", (c) => c.text("Hello Bun!"));

app.get("/api/users", async (c) => {
  const users = await db`SELECT id, name, email FROM users`;
  return c.json(users);
});

app.post("/api/users", async (c) => {
  const body = await c.req.json();
  const { name, email } = body;

  const [user] = await db`
    INSERT INTO users (name, email)
    VALUES (${name}, ${email})
    RETURNING *
  `;

  return c.json(user, 201);
});

// File uploads (con S3 nativo)
app.post("/api/upload", async (c) => {
  const form = await c.req.formData();
  const file = form.get("file") as File;

  const s3 = new S3Client({
    accessKeyId: Bun.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: Bun.env.AWS_SECRET_ACCESS_KEY!,
    region: "us-east-1"
  });

  const key = `uploads/${Date.now()}-${file.name}`;
  await s3.write(`my-bucket/${key}`, file);

  return c.json({ url: `https://my-bucket.s3.amazonaws.com/${key}` });
});

export default {
  port: 3000,
  fetch: app.fetch
};

Ejecutar y Desarrollar

# Correr en desarrollo (con hot reload)
bun --watch run server.ts

# Correr en producción
bun run server.ts

# Scripts de package.json
bun run dev
bun run build
bun run test

Sistema de Tests

Bun tiene test runner integrado compatible con Jest:

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}

export async function fetchUser(id: number) {
  const response = await fetch(`https://api.example.com/users/${id}`);
  return response.json();
}
// math.test.ts
import { describe, test, expect, mock, beforeEach } from "bun:test";
import { add, multiply, fetchUser } from "./math";

describe("Math functions", () => {
  test("add should sum two numbers", () => {
    expect(add(2, 3)).toBe(5);
    expect(add(-1, 1)).toBe(0);
  });

  test("multiply should multiply two numbers", () => {
    expect(multiply(3, 4)).toBe(12);
    expect(multiply(0, 100)).toBe(0);
  });
});

describe("API functions", () => {
  beforeEach(() => {
    // Mock de fetch global
    global.fetch = mock(async (url: string) => {
      return new Response(JSON.stringify({
        id: 1,
        name: "Test User"
      }));
    });
  });

  test("fetchUser should return user data", async () => {
    const user = await fetchUser(1);
    expect(user.name).toBe("Test User");
  });
});
# Correr tests
bun test

# Con coverage
bun test --coverage

# Watch mode
bun test --watch

# Filtrar tests
bun test --filter "Math"

Bundler Integrado

// bun.config.ts (opcional)
export default {
  entrypoints: ["./src/index.tsx"],
  outdir: "./dist",
  target: "browser",
  minify: true,
  sourcemap: "external",
  splitting: true,
  plugins: [
    // Plugins customizados
  ]
};
# Build simple
bun build ./src/index.tsx --outdir ./dist

# Build con minificación
bun build ./src/index.tsx --outdir ./dist --minify

# Build para Node.js
bun build ./src/server.ts --outdir ./dist --target node

# Watch mode
bun build ./src/index.tsx --outdir ./dist --watch

Macros en Tiempo de Build

Una feature única de Bun son macros que corren en tiempo de compilación:

// config.ts
export const VERSION = await Bun.file("./version.txt").text();
export const BUILD_TIME = new Date().toISOString();

// ¡Esto es evaluado en tiempo de build, no en runtime!
export const ENV_CONFIG = {
  apiUrl: Bun.env.API_URL,
  debug: Bun.env.DEBUG === "true"
};
// app.ts
import { VERSION, BUILD_TIME } from "./config" with { type: "macro" };

console.log(`Version: ${VERSION}`);
console.log(`Built at: ${BUILD_TIME}`);

// En el bundle final, esos valores son sustituidos por strings literales

Compatibilidad con Node.js

Bun es altamente compatible con Node.js:

// La mayoría del código Node.js funciona sin cambios
import express from "express";
import { PrismaClient } from "@prisma/client";
import bcrypt from "bcrypt";

const app = express();
const prisma = new PrismaClient();

app.use(express.json());

app.post("/login", async (req, res) => {
  const { email, password } = req.body;

  const user = await prisma.user.findUnique({
    where: { email }
  });

  if (!user) {
    return res.status(401).json({ error: "User not found" });
  }

  const valid = await bcrypt.compare(password, user.password);

  if (!valid) {
    return res.status(401).json({ error: "Invalid password" });
  }

  res.json({ user: { id: user.id, email: user.email } });
});

// Funciona con bun run o node
app.listen(3000);

APIs Node.js Soportadas

// Mayoría de las APIs Node.js funcionan
import fs from "fs";
import path from "path";
import crypto from "crypto";
import { Buffer } from "buffer";
import { EventEmitter } from "events";
import { Readable, Writable } from "stream";

// process global
console.log(process.env.NODE_ENV);
console.log(process.cwd());

// APIs Bun adicionales
const file = Bun.file("./data.json");
const content = await file.text();

const hash = Bun.hash("sha256", "data");

Cuándo Usar Bun

Casos Ideales

1. Proyectos Nuevos que Necesitan Performance:

// APIs de alta performance
// Procesamiento de datos en tiempo real
// Servidores de WebSocket
// Microservicios

Bun.serve({
  port: 3000,
  websocket: {
    message(ws, message) {
      // Broadcast para todos los clientes
      server.publish("chat", message);
    }
  },
  fetch(req, server) {
    if (server.upgrade(req)) {
      return; // Upgrade para WebSocket
    }
    return new Response("Hello!");
  }
});

2. Scripts y Herramientas CLI:

#!/usr/bin/env bun

// Script de migración de datos
import { SQL } from "bun";

const sourceDb = new SQL({ /* config fuente */ });
const targetDb = new SQL({ /* config destino */ });

console.log("Starting migration...");

const users = await sourceDb`SELECT * FROM users`;

for (const user of users) {
  await targetDb`
    INSERT INTO users (id, name, email)
    VALUES (${user.id}, ${user.name}, ${user.email})
  `;
  console.log(`Migrated user ${user.id}`);
}

console.log(`Migration complete! ${users.length} users migrated.`);

3. Cuando Tiempo de Build Importa:

# CI/CD más rápido
# bun install + bun build en segundos
# vs npm install + webpack en minutos

Cuándo Evitar (Por Ahora)

  • Proyectos que dependen de native addons complejos
  • Ecosistemas muy específicos de Node.js
  • Cuando estabilidad es más importante que performance
  • Equipos que no pueden absorber curva de aprendizaje

Migrando de Node.js

Paso 1: Instalar Bun

# Unix/Mac
curl -fsSL https://bun.sh/install | bash

# Windows
powershell -c "irm bun.sh/install.ps1 | iex"

Paso 2: Convertir package.json

# Remover node_modules y lockfiles antiguos
rm -rf node_modules package-lock.json yarn.lock pnpm-lock.yaml

# Instalar con Bun
bun install
# Crea bun.lockb

Paso 3: Testar Compatibilidad

# Correr tests
bun test

# Correr aplicación
bun run dev

# Verificar si todo funciona

Paso 4: Optimizar para Bun

// Sustituir paquetes por APIs nativas cuando posible

// Antes: pg o postgres.js
import { Client } from "pg";

// Después: SQL nativo de Bun
import { SQL } from "bun";

// Antes: aws-sdk para S3
import AWS from "aws-sdk";

// Después: S3 nativo de Bun
import { S3Client } from "bun";

Conclusión

Bun 1.2 representa un avance significativo en el ecosistema JavaScript. Con performance impresionante, APIs nativas para tareas comunes, y excelente compatibilidad con Node.js, se está tornando una opción cada vez más viable para proyectos serios.

La principal recomendación es: experimenta. Comienza con un proyecto nuevo o un script, y ve expandiendo conforme ganes confianza. El ecosistema Bun aún está madurando, pero la dirección es prometedora.

Si quieres comparar Bun con otras opciones de runtime, recomiendo que revises otro artículo: Deno 2.0 vs Node.js: La Batalla de los Runtimes donde vas a descubrir cómo cada runtime se posiciona en el mercado actual.

¡Vamos a por ello! 🦅

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios