Production'da Bun: Neler Çalışıyor, Neler Çalışmıyor ve Neler Beni Şaşırttı
Bun'ı runtime, paket yöneticisi, bundler ve test runner olarak kullanmak. Gerçek benchmark'lar, Node.js uyumluluk boşlukları, migration kalıpları ve bugün Bun'ı production'da nerede kullanıyorum.
Her birkaç yılda bir JavaScript ekosistemi yeni bir runtime kazanır ve söylem öngörülebilir bir yay izler. Heyecan. Benchmark'lar. "X öldü." Gerçeklik kontrolü. Yeni aracın gerçekten parlak olduğu kullanım durumlarına yerleşme.
Bun şu anda o yayın ortasında. Ve çoğu rakibin aksine, kalıcı oluyor. "Daha hızlı" olduğu için değil (çoğu zaman öyle olsa da), çünkü gerçekten farklı bir sorunu çözüyor: JavaScript araç zinciri çok fazla hareketli parçaya sahip ve Bun bunları tek bir şeye sıkıştırıyor.
Bun'ı bir yılı aşkın süredir çeşitli kapasitelerde kullanıyorum. Bir kısmı production'da. Bir kısmı asla değiştirmeyeceğimi düşündüğüm araçların yerine geçti. Bu yazı neyin çalıştığının, neyin çalışmadığının ve boşlukların hâlâ nerede önemli olduğunun dürüst bir muhasebesi.
Bun Aslında Ne#
İlk yanlış anlamayı düzeltelim: Bun "daha hızlı bir Node.js" değil. Bu çerçeveleme onu küçümsüyor.
Bun tek bir binary'de dört araç:
- JavaScript/TypeScript runtime — kodunu çalıştırır, Node.js veya Deno gibi
- Paket yöneticisi — npm, yarn veya pnpm'in yerini alır
- Bundler — belirli kullanım durumları için esbuild, webpack veya Rollup'ın yerini alır
- Test runner — çoğu test suite'i için Jest veya Vitest'in yerini alır
Node.js'den temel mimari fark motor. Node.js V8 (Chrome'un motoru) kullanır. Bun JavaScriptCore (Safari'nin motoru) kullanır. İkisi de olgun, production-grade motorlar ama farklı ödünler veriyorlar. JavaScriptCore daha hızlı başlangıç sürelerine ve daha düşük bellek yüküne meyilli. V8 uzun süren hesaplamalarda daha iyi zirve iş çıkarma hızına meyilli. Pratikte bu farklar çoğu iş yükü için düşündüğünden daha küçük.
Diğer büyük farklılaştırıcı: Bun Zig ile yazılmış — kabaca C ile aynı seviyede duran ama daha iyi bellek güvenliği garantileri olan bir sistem programlama dili. Bun'ın performansta bu kadar agresif olabilmesinin nedeni bu — Zig, C'nin sağladığı türde düşük seviyeli kontrol veriyor ama C'nin ayağına sıkma yoğunluğu olmadan.
# Bun versiyonunu kontrol et
bun --version
# TypeScript dosyasını doğrudan çalıştır — tsconfig yok, derleme adımı yok
bun run server.ts
# Paketleri yükle
bun install
# Testleri çalıştır
bun test
# Production için bundle'la
bun build ./src/index.ts --outdir ./distBu tek bir binary'nin node + npm + esbuild + vitest işini yapması. Sev ya da sevme, bu karmaşıklıkta ikna edici bir azalma.
Hız İddiaları — Dürüst Benchmark'lar#
Bu konuda direkt olayım: Bun'ın pazarlama benchmark'ları seçilmiş. Sahtekârlık değil — seçilmiş. Bun'ın en iyi performans gösterdiği senaryoları gösteriyorlar ki pazarlama materyalinden tam da bunu beklersin. Sorun insanların bu benchmark'lardan Bun'ın her şeyde "25x daha hızlı" olduğu sonucunu çıkarması, ki kesinlikle öyle değil.
İşte Bun'ın gerçekten, anlamlı şekilde daha hızlı olduğu yerler:
Başlangıç Süresi#
Bu Bun'ın en büyük gerçek avantajı ve yakın bile değil.
# Başlangıç süresini ölçme — her birini 100 kez çalıştır
hyperfine --warmup 5 'node -e "console.log(1)"' 'bun -e "console.log(1)"'
# Tipik sonuçlar:
# node: ~40ms
# bun: ~6msBu başlangıç süresinde kabaca 6-7x fark. Script'ler, CLI araçları ve soğuk başlangıcın önemli olduğu serverless fonksiyonlar için bu anlamlı. Bir kez başlayıp haftalarca çalışan uzun ömürlü bir sunucu süreci için alakasız.
Paket Kurulumu#
Rekabeti utandırdığı diğer alan bu.
# Temiz kurulum benchmark'ı — önce node_modules ve lockfile'ı sil
rm -rf node_modules bun.lockb package-lock.json
# npm'i ölç
time npm install
# Real: ~18.4s (tipik orta büyüklükte proje)
# bun'ı ölç
time bun install
# Real: ~2.1sBu 8-9x fark ve tutarlı. Nedenleri başlıca:
- Binary lockfile —
bun.lockbbinary format, JSON değil. Okumak ve yazmak daha hızlı. - Global önbellek — Bun global bir modül önbelleği tutar, böylece projeler arası tekrar yüklemeler indirilen paketleri paylaşır.
- Zig'in I/O'su — Paket yöneticisinin kendisi JavaScript değil Zig ile yazılmış. Dosya I/O operasyonları metale daha yakın.
- Symlink stratejisi — Bun dosya kopyalamak yerine global önbellekten hardlink kullanır.
HTTP Sunucu İş Çıkarma Hızı#
Bun'ın yerleşik HTTP sunucusu hızlı ama karşılaştırmalar bağlam gerektiriyor.
# Bombardier ile hızlı ve kirli benchmark
# Basit "Hello World" yanıtını test etme
# Bun sunucusu
bombardier -c 100 -d 10s http://localhost:3000
# Requests/sec: ~105,000
# Node.js (native http modülü)
bombardier -c 100 -d 10s http://localhost:3001
# Requests/sec: ~48,000
# Node.js (Express)
bombardier -c 100 -d 10s http://localhost:3002
# Requests/sec: ~15,000Bun vs. ham Node.js: önemsiz yanıtlar için kabaca 2x. Bun vs. Express: kabaca 7x ama bu adil değil çünkü Express middleware yükü ekler. Gerçek mantık eklediğin an — veritabanı sorguları, kimlik doğrulama, gerçek verinin JSON serileştirmesi — fark dramatik olarak daralır.
Farkın Önemsiz Olduğu Yerler#
CPU'ya bağlı hesaplama:
// fibonacci.ts — bu motora bağlı, runtime'a bağlı değil
function fib(n: number): number {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
const start = performance.now();
console.log(fib(42));
console.log(`${(performance.now() - start).toFixed(0)}ms`);bun run fibonacci.ts # ~1650ms
node fibonacci.ts # ~1580msNode.js (V8) burada aslında biraz kazanıyor. V8'in JIT derleyicisi sıcak döngülerde daha agresif. CPU'ya bağlı iş için motor farkları belirsiz — bazen V8 kazanır, bazen JSC kazanır ve farklar gürültü seviyesinde.
Kendi Benchmark'larını Nasıl Çalıştırırsın#
Benimkiler dahil kimsenin benchmark'larına güvenme. Senin spesifik iş yükün için önemli olanı nasıl ölçersin:
# Düzgün benchmarking için hyperfine yükle
brew install hyperfine # macOS
# veya: cargo install hyperfine
# Gerçek uygulamanın başlangıç + çalışmasını benchmark'la
hyperfine --warmup 3 \
'node dist/server.js' \
'bun src/server.ts' \
--prepare 'sleep 0.1'
# HTTP sunucuları için bombardier veya wrk kullan
# Önemli: "Hello World" değil gerçekçi payload'larla test et
bombardier -c 50 -d 30s -l http://localhost:3000/api/users
# Bellek karşılaştırması
/usr/bin/time -v node server.js # Linux
/usr/bin/time -l bun server.ts # macOSPratik kural: darboğazın I/O ise (dosya sistemi, ağ, veritabanı), Bun'ın avantajı mütevazı. Darboğazın başlangıç süresi veya araç zinciri hızı ise, Bun büyük kazanır. Darboğazın ham hesaplama ise, sonuç belirsiz.
Paket Yöneticisi Olarak Bun#
Tamamen geçiş yaptığım alan bu. Production'da Node.js çalıştırdığım projelerde bile yerel geliştirme ve CI için bun install kullanıyorum. Sadece daha hızlı ve uyumluluk mükemmel.
Temeller#
# package.json'dan tüm bağımlılıkları yükle
bun install
# Bağımlılık ekle
bun add express
# Dev bağımlılığı ekle
bun add -d vitest
# Bağımlılık kaldır
bun remove express
# Bağımlılık güncelle
bun update express
# Belirli versiyon yükle
bun add express@4.18.2npm veya yarn kullandıysan bu tamamen tanıdık. Flag'ler biraz farklı (--save-dev yerine -d), ama zihinsel model aynı.
Lockfile Durumu#
Bun binary bir lockfile olan bun.lockb kullanır. Bu hem süper gücü hem de en büyük sürtünme noktası.
İyi taraf: Okumak ve yazmak dramatik olarak daha hızlı. Binary format Bun'ın lockfile'ı mikrosaniyeler içinde parse edebilmesi demek, npm'in package-lock.json parse etmeye harcadığı yüzlerce milisaniye değil.
Kötü taraf: Diff'te inceleyemezsin. Bir takımdaysan ve biri bağımlılık güncellerse, PR'daki lockfile diff'ine bakıp neyin değiştiğini göremezsin. Bu hız savunucularının kabul etmek istediğinden daha çok önemli.
# Lockfile'ı okunabilir formata dök
bun bun.lockb > lockfile-dump.txt
# Veya yerleşik metin çıktısını kullan
bun install --yarn
# Bu bun.lockb'nin yanında bir yarn.lock oluştururBenim yaklaşımım: bun.lockb'yi repo'ya commit'liyorum ve okunabilir bir yedek olarak yarn.lock veya package-lock.json de oluşturuyorum. Kemer ve askı.
Workspace Desteği#
Bun npm/yarn tarzı workspace'leri destekliyor:
{
"name": "my-monorepo",
"workspaces": [
"packages/*",
"apps/*"
]
}# Tüm workspace'ler için bağımlılıkları yükle
bun install
# Belirli workspace'te script çalıştır
bun run --filter packages/shared build
# Belirli workspace'e bağımlılık ekle
bun add react --filter apps/webWorkspace desteği sağlam ve önemli ölçüde gelişti. pnpm'e kıyasla ana boşluk Bun'ın workspace bağımlılık çözümlemesinin daha az katı olması — pnpm'in katılığı monorepo'lar için bir özellik çünkü hayalet bağımlılıkları yakalar.
Mevcut Projelerle Uyumluluk#
Neredeyse herhangi bir mevcut Node.js projesine bun install bırakabilirsin. package.json okur, registry yapılandırması için .npmrc'ye uyar ve peerDependencies'i doğru işler. Geçiş genellikle:
# Adım 1: Mevcut lockfile ve node_modules'u sil
rm -rf node_modules package-lock.json yarn.lock pnpm-lock.yaml
# Adım 2: Bun ile yükle
bun install
# Adım 3: Uygulamanın hâlâ çalıştığını doğrula
bun run dev
# veya: node dist/server.js (Bun paket yöneticisi, Node runtime)Bunu bir düzine projede yaptım ve paket yöneticisinin kendisiyle sıfır sorun yaşadım. Tek tuzak CI pipeline'ının özellikle package-lock.json araması — bun.lockb'yi işlemesi için güncellemeniz gerekir.
Node.js Uyumluluğu#
Bu en dikkatli olmam gereken bölüm çünkü durum her ay değişiyor. 2026 başı itibarıyla dürüst tablo şöyle.
Çalışan Şeyler#
npm paketlerinin büyük çoğunluğu değişiklik olmadan çalışıyor. Bun çoğu Node.js yerleşik modülünü uyguluyor:
// Bunların hepsi Bun'da beklendiği gibi çalışır
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import { Buffer } from "node:buffer";
import { EventEmitter } from "node:events";
import { Readable, Writable } from "node:stream";
import http from "node:http";
import https from "node:https";
import { URL, URLSearchParams } from "node:url";
import os from "node:os";
import child_process from "node:child_process";Hem CommonJS hem ESM çalışıyor. require() ve import bir arada yaşayabiliyor. TypeScript herhangi bir derleme adımı olmadan çalışıyor — Bun parse zamanında tip'leri sıyırıyor.
Çalışan framework'ler:
- Express — çalışıyor, middleware ekosistemi dahil
- Fastify — çalışıyor
- Hono — çalışıyor (ve Bun ile mükemmel)
- Next.js — uyarılarla çalışıyor (aşağıda daha fazlası)
- Prisma — çalışıyor
- Drizzle ORM — çalışıyor
- Socket.io — çalışıyor
Çalışmayanlar (veya Sorunlu Olanlar)#
Boşluklar birkaç kategoriye düşme eğiliminde:
Native addon'lar (node-gyp): Bir paket node-gyp ile derlenmiş C++ addon'ları kullanıyorsa Bun'da çalışmayabilir. Bun'ın kendi FFI sistemi var ve birçok native modülü destekliyor ama kapsam %100 değil. Örneğin, bcrypt (native olanı) sorunlar yaşadı — yerine bcryptjs kullan.
# Paketin native addon kullanıp kullanmadığını kontrol et
ls node_modules/your-package/binding.gyp # Bu varsa native'dirSpesifik Node.js dahili bileşenleri: Bazı paketler process.binding() gibi Node.js dahili bileşenlerine ulaşır veya V8'e özgü API'lar kullanır. Bunlar Bun'da çalışmaz çünkü JavaScriptCore üzerinde çalışıyor.
// Bu Bun'da ÇALIŞMAZ — V8'e özgü
const v8 = require("v8");
v8.serialize({ data: "test" });
// Bu ÇALIŞIR — Bun'ın eşdeğerini veya cross-runtime yaklaşımı kullan
const encoded = new TextEncoder().encode(JSON.stringify({ data: "test" }));Worker thread'ler: Bun Web Worker'ları ve node:worker_threads'i destekliyor ama uç durumlar var. Bazı ileri düzey kullanım kalıpları — özellikle SharedArrayBuffer ve Atomics etrafındakiler — farklı davranabiliyor.
vm modülü: node:vm kısmi destekli. Kodun veya bir bağımlılığın yoğun olarak vm.createContext() kullanıyorsa (bazı şablon motorları kullanır), iyice test et.
Uyumluluk Takipçisi#
Bun resmi bir uyumluluk takipçisi tutuyor. Bir proje için Bun'a bağlanmadan önce kontrol et:
# Bun'ın yerleşik uyumluluk kontrolünü projen üzerinde çalıştır
bun --bun node_modules/.bin/your-tool
# --bun flag'i node_modules script'leri için bile Bun'ın runtime'ını zorlarTavsiyem: uyumluluğu varsayma. Karar vermeden önce test suite'ini Bun altında çalıştır. Beş dakika sürer ve saatlerce debug'dan kurtarır.
# Hızlı uyumluluk kontrolü — tam test suite'ini Bun altında çalıştır
bun test # bun test runner kullanıyorsan
# veya
bun run vitest # vitest kullanıyorsanBun'ın Yerleşik API'ları#
Bun'ın ilginçleştiği yer burası. Sadece Node.js API'larını yeniden uygulamak yerine, Bun daha basit ve daha hızlı olacak şekilde tasarlanmış kendi API'larını sunuyor.
Bun.serve() — Yerleşik HTTP Sunucusu#
En çok kullandığım API bu. Temiz, hızlı ve WebSocket desteği yerleşik.
const server = Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response("Hello from Bun!", {
headers: { "Content-Type": "text/plain" },
});
}
if (url.pathname === "/api/users") {
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
return Response.json(users);
}
return new Response("Not Found", { status: 404 });
},
});
console.log(`Server running at http://localhost:${server.port}`);Dikkat edilecek birkaç şey:
- Web-standard Request/Response — tescilli API yok.
fetchhandler'ı standart birRequestalır ve standart birResponsedöner. Cloudflare Worker yazdıysan, bu aynı hissettiriyor. Response.json()— yerleşik JSON response helper'ı.- Import gerekmez —
Bun.serveglobal.require("http")yok.
İşte routing, JSON body parsing ve hata yönetimi ile daha gerçekçi bir örnek:
import { Database } from "bun:sqlite";
const db = new Database("app.db");
db.run(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
completed INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
)
`);
const server = Bun.serve({
port: process.env.PORT || 3000,
async fetch(req) {
const url = new URL(req.url);
const method = req.method;
try {
// GET /api/todos
if (url.pathname === "/api/todos" && method === "GET") {
const todos = db.query("SELECT * FROM todos ORDER BY created_at DESC").all();
return Response.json(todos);
}
// POST /api/todos
if (url.pathname === "/api/todos" && method === "POST") {
const body = await req.json();
if (!body.title || typeof body.title !== "string") {
return Response.json({ error: "Title is required" }, { status: 400 });
}
const stmt = db.prepare("INSERT INTO todos (title) VALUES (?) RETURNING *");
const todo = stmt.get(body.title);
return Response.json(todo, { status: 201 });
}
// DELETE /api/todos/:id
const deleteMatch = url.pathname.match(/^\/api\/todos\/(\d+)$/);
if (deleteMatch && method === "DELETE") {
const id = parseInt(deleteMatch[1], 10);
db.run("DELETE FROM todos WHERE id = ?", [id]);
return new Response(null, { status: 204 });
}
return Response.json({ error: "Not found" }, { status: 404 });
} catch (error) {
console.error("Request error:", error);
return Response.json({ error: "Internal server error" }, { status: 500 });
}
},
});
console.log(`Server running on port ${server.port}`);Bu yaklaşık 50 satırda SQLite ile tam bir CRUD API. Express yok, ORM yok, middleware zinciri yok. Küçük API'lar ve dahili araçlar için bu artık benim varsayılan kurulumum.
Bun.file() ve Bun.write() — Dosya I/O#
Bun'ın dosya API'si fs.readFile()'a kıyasla ferahlatıcı derecede basit:
// Dosya okuma
const file = Bun.file("./config.json");
const text = await file.text(); // String olarak oku
const json = await file.json(); // Doğrudan JSON olarak parse et
const bytes = await file.arrayBuffer(); // ArrayBuffer olarak oku
const stream = file.stream(); // ReadableStream olarak oku
// Dosya metadata'sı
console.log(file.size); // Byte cinsinden boyut
console.log(file.type); // MIME tipi (ör. "application/json")
// Dosya yazma
await Bun.write("./output.txt", "Hello, World!");
await Bun.write("./data.json", JSON.stringify({ key: "value" }));
await Bun.write("./copy.png", Bun.file("./original.png"));
// Bir Response body'sini dosyaya yaz
const response = await fetch("https://example.com/data.json");
await Bun.write("./downloaded.json", response);Bun.file() API'si tembel — .text(), .json() vs. çağırana kadar dosyayı okumaz. Bu Bun.file() referanslarını gerçekten veriye ihtiyaç duyana kadar I/O maliyeti olmadan taşıyabilmen demek.
Yerleşik WebSocket Desteği#
WebSocket'ler Bun.serve()'da birinci sınıf:
const server = Bun.serve({
port: 3000,
fetch(req, server) {
const url = new URL(req.url);
if (url.pathname === "/ws") {
const upgraded = server.upgrade(req, {
data: {
userId: url.searchParams.get("userId"),
joinedAt: Date.now(),
},
});
if (!upgraded) {
return new Response("WebSocket upgrade failed", { status: 400 });
}
return undefined;
}
return new Response("Use /ws for WebSocket connections");
},
websocket: {
open(ws) {
console.log(`Client connected: ${ws.data.userId}`);
ws.subscribe("chat");
},
message(ws, message) {
// Tüm subscriber'lara yayınla
server.publish("chat", `${ws.data.userId}: ${message}`);
},
close(ws) {
console.log(`Client disconnected: ${ws.data.userId}`);
ws.unsubscribe("chat");
},
},
});server.publish() ve ws.subscribe() kalıbı yerleşik pub/sub. Redis yok, ayrı WebSocket kütüphanesi yok. Basit gerçek zamanlı özellikler için bu inanılmaz kullanışlı.
bun:sqlite ile Yerleşik SQLite#
Beni en çok şaşırtan bu oldu. Bun SQLite'ı runtime'ın içine yerleştirilmiş olarak getiriyor:
import { Database } from "bun:sqlite";
// Veritabanı aç veya oluştur
const db = new Database("myapp.db");
// Daha iyi eşzamanlı okuma performansı için WAL modu
db.exec("PRAGMA journal_mode = WAL");
// Tablo oluştur
db.run(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
`);
// Prepared statement'lar (tekrar kullanılabilir, tekrarlanan sorgular için daha hızlı)
const insertUser = db.prepare(
"INSERT INTO users (email, name) VALUES ($email, $name) RETURNING *"
);
const findByEmail = db.prepare(
"SELECT * FROM users WHERE email = $email"
);
// Kullanım
const user = insertUser.get({
$email: "alice@example.com",
$name: "Alice",
});
console.log(user); // { id: 1, email: "alice@example.com", name: "Alice", ... }
// Transaction'lar
const insertMany = db.transaction((users: { email: string; name: string }[]) => {
for (const user of users) {
insertUser.run({ $email: user.email, $name: user.name });
}
return users.length;
});
const count = insertMany([
{ email: "bob@example.com", name: "Bob" },
{ email: "carol@example.com", name: "Carol" },
]);
console.log(`Inserted ${count} users`);Bu bir C kütüphanesinin performansıyla senkron SQLite (çünkü öyle — Bun doğrudan libsqlite3'ü gömüyor). CLI araçları, local-first uygulamalar ve küçük servisler için yerleşik SQLite veri katmanın için sıfır harici bağımlılık demek.
Bun Test Runner#
bun test çoğu durumda Jest'in yerine geçen bir araç. Aynı describe/it/expect API'sını kullanıyor ve çoğu Jest matcher'ını destekliyor.
Temel Kullanım#
// math.test.ts
import { describe, it, expect } from "bun:test";
describe("math utilities", () => {
it("adds numbers correctly", () => {
expect(1 + 2).toBe(3);
});
it("handles floating point", () => {
expect(0.1 + 0.2).toBeCloseTo(0.3);
});
});# Tüm testleri çalıştır
bun test
# Belirli dosyayı çalıştır
bun test math.test.ts
# Kalıba uyan testleri çalıştır
bun test --test-name-pattern "adds numbers"
# Watch modu
bun test --watch
# Coverage
bun test --coverageMocking#
Bun Jest-uyumlu mocking destekliyor:
import { describe, it, expect, mock, spyOn } from "bun:test";
import { fetchUsers } from "./api";
// Modül mock'la
mock.module("./database", () => ({
query: mock(() => [{ id: 1, name: "Alice" }]),
}));
describe("fetchUsers", () => {
it("returns users from database", async () => {
const users = await fetchUsers();
expect(users).toHaveLength(1);
expect(users[0].name).toBe("Alice");
});
});
// Nesne metoduna spy koy
describe("console", () => {
it("tracks console.log calls", () => {
const logSpy = spyOn(console, "log");
console.log("test message");
expect(logSpy).toHaveBeenCalledWith("test message");
logSpy.mockRestore();
});
});Bun Test vs. Vitest — Dürüst Karşılaştırmam#
Bu proje (ve çoğu projem) için Vitest kullanıyorum. Tamamen geçiş yapmamamın nedeni:
bun test'in kazandığı yerler:
- Başlangıç hızı.
bun testVitest config'ini yüklemeyi bitirmeden testleri çalıştırmaya başlar. - Sıfır yapılandırma. Temel kurulumlar için
vitest.config.tsgerekmez. - Yerleşik TypeScript. Dönüşüm adımı yok.
Vitest'in hâlâ kazandığı yerler:
- Ekosistem: Vitest'in daha fazla plugin'i, daha iyi IDE entegrasyonu ve daha büyük topluluğu var.
- Yapılandırma: Vitest'in yapılandırma sistemi daha esnek. Özel reporter'lar, karmaşık setup dosyaları, birden fazla test ortamı.
- Tarayıcı modu: Vitest testleri gerçek bir tarayıcıda çalıştırabilir. Bun yapamaz.
- Uyumluluk: Bazı test kütüphaneleri (Testing Library, MSW) Vitest/Jest ile daha kapsamlı test edilmiş.
- Snapshot testing: İkisi de destekliyor ama Vitest'in uygulaması daha olgun ve daha iyi diff çıktısı var.
Basit test ihtiyaçları olan yeni bir proje için bun test kullanırdım. Testing Library, MSW ve karmaşık mocking'li mevcut bir proje için Vitest'i tutuyorum.
Bun Bundler#
bun build hızlı bir JavaScript/TypeScript bundler. webpack'in yerini alan bir şey değil — daha çok esbuild kategorisinde: hızlı, görüşlü ve yaygın durumlara odaklı.
Temel Bundling#
# Tek bir giriş noktasını bundle'la
bun build ./src/index.ts --outdir ./dist
# Farklı hedefler için bundle'la
bun build ./src/index.ts --outdir ./dist --target browser
bun build ./src/index.ts --outdir ./dist --target bun
bun build ./src/index.ts --outdir ./dist --target node
# Minify
bun build ./src/index.ts --outdir ./dist --minify
# Sourcemap oluştur
bun build ./src/index.ts --outdir ./dist --sourcemap externalProgramatik API#
const result = await Bun.build({
entrypoints: ["./src/index.ts", "./src/worker.ts"],
outdir: "./dist",
target: "browser",
minify: {
whitespace: true,
identifiers: true,
syntax: true,
},
splitting: true, // Kod bölme
sourcemap: "external",
external: ["react", "react-dom"], // Bunları bundle'lama
naming: "[dir]/[name]-[hash].[ext]",
define: {
"process.env.NODE_ENV": JSON.stringify("production"),
},
});
if (!result.success) {
console.error("Build failed:");
for (const log of result.logs) {
console.error(log);
}
process.exit(1);
}
for (const output of result.outputs) {
console.log(`${output.path} — ${output.size} bytes`);
}Tree-Shaking#
Bun ESM için tree-shaking destekliyor:
// utils.ts
export function used() {
return "I'll be in the bundle";
}
export function unused() {
return "I'll be tree-shaken away";
}
// index.ts
import { used } from "./utils";
console.log(used());bun build ./src/index.ts --outdir ./dist --minify
# `unused` fonksiyonu çıktıda görünmeyecekBun Build'in Yetersiz Kaldığı Yerler#
- CSS bundling yok — CSS için ayrı bir araç gerekir (PostCSS, Lightning CSS, Tailwind CLI).
- HTML üretimi yok — JavaScript/TypeScript bundle'lıyor, tam web uygulamaları değil.
- Plugin ekosistemi — esbuild'in çok daha büyük bir plugin ekosistemi var. Bun'ın plugin API'si uyumlu ama topluluk daha küçük.
- İleri düzey kod bölme — Webpack ve Rollup hâlâ daha sofistike chunk stratejileri sunuyor.
Bir kütüphane veya basit bir web uygulamasının JS bundle'ını oluşturmak için bun build mükemmel. CSS modülleri, görüntü optimizasyonu ve özel chunk stratejileri olan karmaşık uygulama build'leri için hâlâ tam bir bundler isteyeceksin.
Bun Macro'ları#
Gerçekten benzersiz bir özellik: macro'lar aracılığıyla derleme zamanı kod çalıştırma.
// build-info.ts — bu dosya DERLEME ZAMANINDA çalışır, runtime'da değil
export function getBuildInfo() {
return {
builtAt: new Date().toISOString(),
gitSha: require("child_process")
.execSync("git rev-parse --short HEAD")
.toString()
.trim(),
nodeVersion: process.version,
};
}// app.ts
import { getBuildInfo } from "./build-info" with { type: "macro" };
// getBuildInfo() bundle zamanında çalışır
// Sonuç statik bir değer olarak satır içine alınır
const info = getBuildInfo();
console.log(`Built at ${info.builtAt}, commit ${info.gitSha}`);Bundling'den sonra getBuildInfo() literal nesneyle değiştirilir — runtime'da fonksiyon çağrısı yok, child_process import'u yok. Kod build sırasında çalıştı ve sonuç satır içine alındı. Build metadata'sı, feature flag'leri veya ortama özgü yapılandırma gömmek için güçlü bir yöntem.
Bun'ı Next.js ile Kullanmak#
En çok sorulan soru bu, o yüzden çok spesifik olayım.
Bugün Çalışanlar#
Next.js için paket yöneticisi olarak Bun — mükemmel çalışıyor:
# Bağımlılıkları yüklemek için Bun kullan, sonra Next.js'i çalıştırmak için Node.js kullan
bun install
bun run dev # Aslında "dev" script'ini varsayılan olarak Node.js üzerinden çalıştırır
bun run build
bun run startHer Next.js projesinde yaptığım bu. bun run <script> komutu package.json'ın scripts bölümünü okur ve çalıştırır. Varsayılan olarak gerçek çalıştırma için sistemin Node.js'ini kullanır. Runtime'ını değiştirmeden Bun'ın hızlı paket kurulumunun avantajını alıyorsun.
Next.js geliştirme için Bun runtime:
# Next.js'i Bun'ın runtime'ı altında çalışmaya zorla
bun --bun run devBu geliştirme için çoğu durumda çalışıyor. --bun flag'i Bun'a Node.js'e delege etmek yerine kendi runtime'ını kullanmasını söyler. Hot module replacement çalışıyor. API route'ları çalışıyor. Server component'ler çalışıyor.
Hâlâ Deneysel Olanlar#
Next.js production build'leri için Bun runtime:
# Bun runtime ile build et
bun --bun run build
# Production sunucusunu Bun runtime ile başlat
bun --bun run startBu birçok proje için çalışıyor ama uç durumlarla karşılaştım:
- Bazı middleware davranışları farklı — Node.js'e özgü API'lara bağlı Next.js middleware kullanıyorsan uyumluluk sorunlarına düşebilirsin.
- Görüntü optimizasyonu — Next.js'in görüntü optimizasyon pipeline'ı native addon olan sharp kullanıyor. Bun ile çalışıyor ama ara sıra sorunlar gördüm.
- ISR (Incremental Static Regeneration) — Çalışıyor ama Bun altında production'da stres testi yapmadım.
Next.js İçin Tavsiyem#
Bun'ı paket yöneticisi olarak kullan. Node.js'i runtime olarak kullan. Bu sana herhangi bir uyumluluk riski olmadan bun install'ın hız avantajlarını verir.
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start"
}
}# Günlük iş akışı
bun install # Hızlı paket kurulumu
bun run dev # "next dev"i Node.js üzerinden çalıştırır
bun run build # "next build"i Node.js üzerinden çalıştırırBun'ın Node.js uyumluluğu Next.js'in dahili kullanımı için %100'e ulaştığında (yakın ama henüz orada değil) geçiş yapacağım. O zamana kadar tek başına paket yöneticisi bile bana kurulumu haklı çıkaracak kadar zaman kazandırıyor.
Docker ile Bun#
Resmi Bun Docker image'ı iyi bakımlı ve production'a hazır.
Temel Dockerfile#
FROM oven/bun:1 AS base
WORKDIR /app
# Bağımlılıkları yükle
FROM base AS deps
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
# Build (gerekirse)
FROM base AS build
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
# Production
FROM base AS production
WORKDIR /app
# Root olarak çalıştırma
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
USER appuser
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./
EXPOSE 3000
CMD ["bun", "run", "dist/server.js"]Minimal Image İçin Çok Aşamalı Build#
# Build aşaması: tüm bağımlılıklarla tam Bun image'ı
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build ./src/index.ts --target bun --outdir ./dist --minify
# Runtime aşaması: daha küçük base image
FROM oven/bun:1-slim AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
USER appuser
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["bun", "run", "dist/index.js"]Tek Binary'ye Derleme#
Bu Bun'ın deployment için en güçlü özelliklerinden biri:
# Uygulamanı tek bir çalıştırılabilir dosyaya derle
bun build --compile ./src/server.ts --outfile server
# Çıktı bağımsız bir binary — çalıştırmak için Bun veya Node.js gerekmez
./server# Derlenmiş binary kullanan ultra-minimal Docker image
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build --compile ./src/server.ts --outfile server
# Final image — sadece binary
FROM debian:bookworm-slim
WORKDIR /app
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
USER appuser
COPY --from=builder /app/server ./server
EXPOSE 3000
CMD ["./server"]Derlenmiş binary genellikle 50-90 MB (Bun runtime'ını bundle'lıyor). Bu Go binary'sinden büyük ama tam Node.js kurulumu artı node_modules'tan çok daha küçük. Konteynerize deployment'lar için bağımsız yapı önemli bir basitleştirme.
Boyut Karşılaştırması#
# Node.js image'ı
docker images | grep node
# node:20-slim ~180MB
# Bun image'ı
docker images | grep bun
# oven/bun:1-slim ~130MB
# debian:bookworm-slim üzerinde derlenmiş binary
# ~80MB base + ~70MB binary = ~150MB toplam
# Alpine ile Node.js
# node:20-alpine ~130MB + node_modulesBinary yaklaşımı node_modules'u final image'dan tamamen ortadan kaldırır. Production'da npm install yok. Yüzlerce paketten gelen tedarik zinciri saldırı yüzeyi yok. Sadece tek dosya.
Migration Kalıpları#
Bun'a geçişi düşünüyorsan, tavsiye ettiğim kademeli yol:
Faz 1: Sadece Paket Yöneticisi (Sıfır Risk)#
# npm/yarn/pnpm'i bun install ile değiştir
# CI pipeline'ını değiştir:
# Önce:
npm ci
# Sonra:
bun install --frozen-lockfileKod değişikliği yok. Runtime değişikliği yok. Sadece daha hızlı kurulumlar. Bir şey bozulursa (bozulmayacak), bun.lockb'yi silip npm install çalıştırarak geri al.
Faz 2: Script'ler ve Araçlar#
# Geliştirme script'lerini çalıştırmak için bun kullan
bun run dev
bun run lint
bun run format
# Tek seferlik script'ler için bun kullan
bun run scripts/seed-database.ts
bun run scripts/migrate.tsGerçek uygulamanın runtime'ı için hâlâ Node.js kullanılıyor. Ama script'ler Bun'ın daha hızlı başlangıcından ve native TypeScript desteğinden faydalanıyor.
Faz 3: Test Runner (Orta Risk)#
# Basit test suite'leri için vitest/jest'i bun test ile değiştir
bun test
# Karmaşık test kurulumları için vitest'i tut
# (Testing Library, MSW, özel ortamlar)Tam test suite'ini bun test altında çalıştır. Her şey geçerse bir devDependency'den kurtuldun. Bazı testler uyumluluk nedeniyle başarısız olursa onlar için Vitest'i tut, geri kalanı için bun test kullan.
Faz 4: Yeni Servisler İçin Runtime (Hesaplı Risk)#
// Yeni mikroservisler veya API'lar — ilk günden Bun ile başla
Bun.serve({
port: 3000,
fetch(req) {
// Yeni servisin burada
},
});Mevcut Node.js servislerini Bun runtime'ına migrate etme. Bunun yerine yeni servisleri başından Bun ile yaz. Bu patlama yarıçapını sınırlar.
Faz 5: Runtime Migration (İleri Düzey)#
# Sadece kapsamlı testten sonra:
# Mevcut servisler için node'u bun ile değiştir
# Önce:
node dist/server.js
# Sonra:
bun dist/server.jsBunu sadece mükemmel test kapsamına sahip servisler için tavsiye ederim. Production'ı değiştirmeden önce yük testlerini Bun altında çalıştır.
Ortam Değişkenleri ve Yapılandırma#
Bun .env dosyalarını otomatik olarak işler — dotenv paketine gerek yok:
# .env
DATABASE_URL=postgresql://localhost:5432/myapp
API_KEY=sk-test-12345
PORT=3000// Bunlar hiçbir import olmadan kullanılabilir
console.log(process.env.DATABASE_URL);
console.log(process.env.API_KEY);
console.log(Bun.env.PORT); // Bun'a özgü alternatifBun .env, .env.local, .env.production vs. dosyalarını Next.js ile aynı konvansiyonu izleyerek otomatik olarak yükler. package.json'ında bir bağımlılık daha az.
Hata Yönetimi ve Debug#
Bun'ın hata çıktısı önemli ölçüde gelişti ama bazı durumlarda hâlâ Node.js kadar cilalı değil:
# Bun'ın debugger'ı — VS Code ile çalışır
bun --inspect run server.ts
# Bun'ın inspect-brk'si — ilk satırda dur
bun --inspect-brk run server.tsVS Code için .vscode/launch.json'una bunu ekle:
{
"version": "0.2.0",
"configurations": [
{
"type": "bun",
"request": "launch",
"name": "Debug Bun",
"program": "${workspaceFolder}/src/server.ts",
"cwd": "${workspaceFolder}",
"stopOnEntry": false,
"watchMode": false
}
]
}Bun'daki stack trace'ler genellikle doğru ve TypeScript için source map'ler içeriyor. Ana debug boşluğu bazı Node.js'e özgü debug araçlarının (örneğin ndb veya clinic.js) Bun ile çalışmaması.
Güvenlik Değerlendirmeleri#
Production için Bun'ı değerlendiriyorsan düşünülmesi gereken birkaç şey:
Olgunluk: Node.js 15+ yıldır production'da. HTTP parsing, TLS işleme ve stream processing'deki her uç durum bulunmuş ve düzeltilmiş. Bun daha genç. İyi test edilmiş ama keşfedilmemiş hatalar için yüzey alanı daha geniş.
Güvenlik yamaları: Bun ekibi güncellemeleri sık gönderiyor ama Node.js güvenlik ekibinin resmi bir CVE süreci, koordineli ifşa ve daha uzun bir geçmişi var. Güvenlik açısından kritik uygulamalar için bu önemli.
Tedarik zinciri: Bun'ın yerleşik özellikleri (SQLite, HTTP sunucusu, WebSocket'ler) daha az npm bağımlılığı demek. Daha az bağımlılık daha küçük tedarik zinciri saldırı yüzeyi demek. Bu gerçek bir güvenlik avantajı.
# Bağımlılık sayılarını karşılaştır
# Tipik bir Express + SQLite + WebSocket projesi:
npm ls --all | wc -l
# ~340 paket
# Aynı işlevsellik Bun yerleşik özellikleriyle:
bun pm ls --all | wc -l
# ~12 paket (sadece uygulama kodun)Bu production iş yüküne güvendiğin paket sayısında anlamlı bir azalma.
Performans Ayarları#
Birkaç Bun'a özgü performans ipucu:
// Production ayarları için Bun.serve() seçeneklerini kullan
Bun.serve({
port: 3000,
// Maksimum istek gövde boyutunu artır (varsayılan 128MB)
maxRequestBodySize: 1024 * 1024 * 50, // 50MB
// Daha iyi hata sayfaları için geliştirme modunu etkinleştir
development: process.env.NODE_ENV !== "production",
// Port'u yeniden kullan (sıfır kesinti yeniden başlatmalar için faydalı)
reusePort: true,
fetch(req) {
return new Response("OK");
},
});// Runtime kod dönüşümü için Bun.Transpiler kullan
const transpiler = new Bun.Transpiler({
loader: "tsx",
target: "browser",
});
const code = transpiler.transformSync(`
const App: React.FC = () => <div>Hello</div>;
export default App;
`);# Bun'ın bellek kullanımı flag'leri
bun --smol run server.ts # Bellek ayak izini azalt (biraz daha yavaş)
# Maksimum heap boyutunu ayarla
BUN_JSC_forceRAMSize=512000000 bun run server.ts # ~512MB limitYaygın Tuzaklar#
Bir yıllık Bun kullanımından sonra beni tuzağa düşüren şeyler:
1. Global Fetch Davranışı Farklı#
// Node.js 18+ fetch ve Bun'ın fetch'i belirli header'ları
// ve yönlendirmeleri nasıl işlediği konusunda biraz farklı
// Bun yönlendirmeleri varsayılan olarak takip eder (tarayıcılar gibi)
// Node.js fetch de yönlendirmeleri takip eder ama belirli durum kodlarıyla
// (303, 307, 308) davranış farklı olabilir
const response = await fetch("https://api.example.com/data", {
redirect: "manual", // Yönlendirme işleme konusunda açık ol
});2. Process Çıkış Davranışı#
// Bun olay döngüsü boşaldığında çıkar
// Node.js bazen kalan handle'lar yüzünden çalışmaya devam eder
// Bun script'in beklenmedik şekilde çıkıyorsa, bir şey
// olay döngüsünü canlı tutmuyor
// Bu Bun'da hemen çıkar:
setTimeout(() => {}, 0);
// Bu çalışmaya devam eder:
setTimeout(() => {}, 1000);
// (Bun timeout tetiklendikten sonra çıkar)3. TypeScript Yapılandırması#
// Bun'ın kendi tsconfig varsayılanları var
// Bir projeyi Bun ve Node.js arasında paylaşıyorsan
// tsconfig.json'ında açık ol:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["bun-types"] // Bun tip tanımlarını ekle
}
}# Bun tiplerini yükle
bun add -d @types/bun4. Geliştirmede Hot Reload#
# Bun'ın yerleşik watch modu
bun --watch run server.ts
# Bu dosya değişikliklerinde süreci yeniden başlatır
# HMR (Hot Module Replacement) değil — tam yeniden başlatma
# Ama Bun o kadar hızlı başladığı için anlık hissettiriyor5. bunfig.toml Yapılandırma Dosyası#
# bunfig.toml — Bun'ın yapılandırma dosyası (isteğe bağlı)
[install]
# Özel registry kullan
registry = "https://npm.mycompany.com"
# Scope'lu registry'ler
[install.scopes]
"@mycompany" = "https://npm.mycompany.com"
[test]
# Test yapılandırması
coverage = true
coverageReporter = ["text", "lcov"]
[run]
# bun run için shell
shell = "bash"Kararım#
Bir yıllık production kullanımından sonra vardığım nokta:
Bugün Bun'ı Nerede Kullanıyorum#
Tüm projeler için paket yöneticisi — bu Next.js blog dahil. bun install daha hızlı ve uyumluluk esasen mükemmel. npm veya yarn kullanmak için bir neden görmüyorum artık. pnpm düşüneceğim tek alternatif (monorepo'lardaki katı bağımlılık çözümlemesi için).
Script'ler ve CLI araçları için runtime — Bir kez çalıştırmam gereken herhangi bir TypeScript dosyasını bun ile çalıştırıyorum. Derleme adımı yok. Hızlı başlangıç. Yerleşik .env yükleme. İş akışımda ts-node ve tsx'in yerini tamamen aldı.
Küçük API'lar ve dahili araçlar için runtime — Bun.serve() + bun:sqlite dahili araçlar, webhook handler'ları ve küçük servisler için inanılmaz üretken bir yığın. "Tek binary, sıfır bağımlılık" deployment modeli ikna edici.
Basit projeler için test runner — Basit test ihtiyaçları olan projeler için bun test hızlı ve sıfır yapılandırma gerektiriyor.
Node.js'te Kaldığım Yerler#
Production Next.js — Bun'ın çalışmadığı için değil, risk-ödül oranının henüz haklı kılmadığı için. Next.js birçok entegrasyon noktası olan karmaşık bir framework. Altında en savaş testinden geçmiş runtime'ı istiyorum.
Kritik production servisleri — Ana API sunucularım PM2 arkasında Node.js çalıştırıyor. İzleme ekosistemi, debug araçları, operasyonel bilgi birikimi — hepsi Node.js. Bun oraya varacak ama henüz orada değil.
Native addon'lu her şey — Bağımlılık zinciri C++ native addon'ları içeriyorsa Bun'ı denemeye bile gerek yok. Uyumluluk sorunlarını debug etmeye değmez.
Bun'a aşina olmayan takımlar — Hiç kullanmamış bir takıma runtime olarak Bun'ı tanıtmak bilişsel yük ekler. Paket yöneticisi olarak tamam. Runtime olarak takım hazır olana kadar bekle.
Neler İzliyorum#
Bun'ın uyumluluk takipçisi — Beni ilgilendiren Node.js API'ları için %100'e ulaştığında yeniden değerlendireceğim.
Framework desteği — Next.js, Remix ve SvelteKit'in hepsinin farklı düzeylerde Bun desteği var. Biri resmi olarak Bun'ı production runtime olarak desteklediğinde bu bir sinyal.
Kurumsal benimseme — Gerçek SLA'ları olan şirketler production'da Bun çalıştırıp bunu yazdığında olgunluk sorusu cevaplanmış olur.
1.2+ sürüm hattı — Bun hızlı ilerliyor. Her hafta özellikler geliyor. Bugün kullandığım Bun, bir yıl önce denediğim Bun'dan anlamlı şekilde daha iyi.
Sonuç#
Bun sihirli bir çözüm değil. Yavaş bir uygulamayı hızlı, kötü tasarlanmış bir API'yı iyi tasarlanmış yapmayacak. Ama JavaScript ekosistemi için geliştirici deneyiminde gerçek bir iyileştirme.
Bun'da en çok takdir ettiğim şey tek bir özellik değil. Araç zinciri karmaşıklığındaki azalma. Paketleri yükleyen, TypeScript çalıştıran, kodu bundle'layan ve testleri çalıştıran tek bir binary. Script'ler için tsconfig.json yok. Babel yok. Ayrı test runner yapılandırması yok. Sadece bun run dosyan.ts ve çalışıyor.
Pratik tavsiye: bun install ile başla. Sıfır risk, anında fayda. Sonra script'ler için bun run'ı dene. Sonra gerisini spesifik ihtiyaçlarına göre değerlendir. Tamamen geçiş yapmak zorunda değilsin. Bun kısmi bir yedek olarak mükemmel çalışıyor ve muhtemelen çoğu insanın bugün onu kullanması gereken yol bu.
JavaScript runtime manzarası Bun'la birlikte daha iyi. Rekabet Node.js'i de daha iyi yapıyor — Node.js 22+ kısmen Bun'ın baskısına yanıt olarak önemli ölçüde hızlandı. Herkes kazanıyor.