Bun di Produksi: Apa yang Berhasil, Apa yang Tidak, dan Apa yang Mengejutkan Saya
Bun sebagai runtime, package manager, bundler, dan test runner. Benchmark nyata, celah kompatibilitas Node.js, pola migrasi, dan di mana saya menggunakan Bun di produksi saat ini.
Setiap beberapa tahun, ekosistem JavaScript mendapatkan runtime baru dan diskursusnya mengikuti alur yang sudah bisa ditebak. Hype. Benchmark. "X sudah mati." Cek realita. Kemudian menetap pada kasus penggunaan nyata di mana alat baru tersebut benar-benar bersinar.
Bun sedang berada di tengah-tengah alur itu sekarang. Dan berbeda dengan kebanyakan penantang, ia tetap bertahan. Bukan karena "lebih cepat" (meskipun memang sering begitu), tetapi karena ia memecahkan masalah yang benar-benar berbeda: toolchain JavaScript punya terlalu banyak komponen bergerak, dan Bun menyatukannya menjadi satu.
Saya sudah menggunakan Bun dalam berbagai kapasitas selama lebih dari setahun. Sebagian di produksi. Sebagian menggantikan alat-alat yang saya kira tidak akan pernah saya ganti. Tulisan ini adalah catatan jujur tentang apa yang berhasil, apa yang tidak, dan di mana celahnya masih penting.
Apa Sebenarnya Bun Itu#
Kesalahpahaman pertama yang perlu diluruskan: Bun bukan "Node.js yang lebih cepat." Framing seperti itu meremehkannya.
Bun adalah empat alat dalam satu binary:
- Runtime JavaScript/TypeScript — menjalankan kode Anda, seperti Node.js atau Deno
- Package manager — menggantikan npm, yarn, atau pnpm
- Bundler — menggantikan esbuild, webpack, atau Rollup untuk kasus penggunaan tertentu
- Test runner — menggantikan Jest atau Vitest untuk sebagian besar test suite
Perbedaan arsitektur utama dari Node.js adalah engine-nya. Node.js menggunakan V8 (engine Chrome). Bun menggunakan JavaScriptCore (engine Safari). Keduanya adalah engine yang matang dan layak produksi, tetapi mereka membuat trade-off yang berbeda. JavaScriptCore cenderung memiliki waktu startup lebih cepat dan overhead memori lebih rendah. V8 cenderung memiliki throughput puncak lebih baik untuk komputasi yang berjalan lama. Dalam praktiknya, perbedaan ini lebih kecil dari yang Anda kira untuk sebagian besar beban kerja.
Pembeda utama lainnya: Bun ditulis dalam Zig, bahasa pemrograman sistem yang berada di level yang kurang lebih sama dengan C tetapi dengan jaminan keamanan memori yang lebih baik. Inilah mengapa Bun bisa begitu agresif dalam hal performa — Zig memberikan kontrol level rendah yang sama seperti C tanpa tingkat bahaya yang tinggi dari C.
# Check your Bun version
bun --version
# Run a TypeScript file directly — no tsconfig, no compilation step
bun run server.ts
# Install packages
bun install
# Run tests
bun test
# Bundle for production
bun build ./src/index.ts --outdir ./distItu satu binary yang mengerjakan tugas node + npm + esbuild + vitest. Suka atau tidak, itu adalah pengurangan kompleksitas yang meyakinkan.
Klaim Kecepatan — Benchmark yang Jujur#
Izinkan saya bicara terus terang soal ini: benchmark marketing Bun itu cherry-picked. Bukan palsu — cherry-picked. Mereka menunjukkan skenario di mana Bun berkinerja terbaik, yang persis seperti yang Anda harapkan dari materi pemasaran. Masalahnya adalah orang-orang mengekstrapolasi dari benchmark tersebut untuk mengklaim Bun "25x lebih cepat" di segala hal, yang sama sekali tidak benar.
Berikut di mana Bun benar-benar, secara signifikan lebih cepat:
Waktu Startup#
Ini adalah keunggulan terbesar Bun yang sesungguhnya dan jaraknya sangat jauh.
# Measuring startup time — run each 100 times
hyperfine --warmup 5 'node -e "console.log(1)"' 'bun -e "console.log(1)"'
# Typical results:
# node: ~40ms
# bun: ~6msItu kira-kira perbedaan 6-7x dalam waktu startup. Untuk skrip, alat CLI, dan fungsi serverless di mana cold start penting, ini signifikan. Untuk proses server yang berjalan lama yang dimulai sekali dan berjalan selama berminggu-minggu, ini tidak relevan.
Instalasi Paket#
Ini adalah area lain di mana Bun membuat malu kompetitornya.
# Clean install benchmark — delete node_modules and lockfile first
rm -rf node_modules bun.lockb package-lock.json
# Time npm
time npm install
# Real: ~18.4s (typical medium-sized project)
# Time bun
time bun install
# Real: ~2.1sItu perbedaan 8-9x, dan konsisten. Alasannya terutama:
- Lockfile biner —
bun.lockbadalah format biner, bukan JSON. Lebih cepat dibaca dan ditulis. - Cache global — Bun memelihara cache modul global sehingga instalasi ulang di berbagai proyek berbagi paket yang sudah diunduh.
- I/O Zig — Package manager itu sendiri ditulis dalam Zig, bukan JavaScript. Operasi I/O file lebih dekat ke metal.
- Strategi symlink — Bun menggunakan hardlink dari cache global alih-alih menyalin file.
Throughput HTTP Server#
HTTP server bawaan Bun memang cepat, tapi perbandingannya butuh konteks.
# Quick and dirty benchmark with bombardier
# Testing a simple "Hello World" response
# Bun server
bombardier -c 100 -d 10s http://localhost:3000
# Requests/sec: ~105,000
# Node.js (native http module)
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. Node.js mentah: kira-kira 2x untuk respons trivial. Bun vs. Express: kira-kira 7x, tapi itu tidak adil karena Express menambahkan overhead middleware. Begitu Anda menambahkan logika nyata — query database, autentikasi, serialisasi JSON dari data aktual — jaraknya menyempit drastis.
Di Mana Perbedaannya Negligibel#
Komputasi CPU-bound:
// fibonacci.ts — this is engine-bound, not runtime-bound
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) sebenarnya sedikit menang di sini. JIT compiler V8 lebih agresif pada hot loop. Untuk pekerjaan CPU-bound, perbedaan engine-nya impas — terkadang V8 menang, terkadang JSC menang, dan perbedaannya masih dalam noise.
Cara Menjalankan Benchmark Anda Sendiri#
Jangan percaya benchmark siapa pun, termasuk milik saya. Berikut cara mengukur apa yang penting untuk beban kerja spesifik Anda:
# Install hyperfine for proper benchmarking
brew install hyperfine # macOS
# or: cargo install hyperfine
# Benchmark startup + execution of your actual app
hyperfine --warmup 3 \
'node dist/server.js' \
'bun src/server.ts' \
--prepare 'sleep 0.1'
# For HTTP servers, use bombardier or wrk
# Important: test with realistic payloads, not "Hello World"
bombardier -c 50 -d 30s -l http://localhost:3000/api/users
# Memory comparison
/usr/bin/time -v node server.js # Linux
/usr/bin/time -l bun server.ts # macOSAturan praktisnya: jika bottleneck Anda adalah I/O (file system, jaringan, database), keunggulan Bun moderat. Jika bottleneck Anda adalah waktu startup atau kecepatan toolchain, Bun menang besar. Jika bottleneck Anda adalah komputasi mentah, hasilnya imbang.
Bun sebagai Package Manager#
Di sinilah saya sudah sepenuhnya beralih. Bahkan pada proyek di mana saya menjalankan Node.js di produksi, saya menggunakan bun install untuk pengembangan lokal dan CI. Ini memang lebih cepat, dan kompatibilitasnya sangat baik.
Dasar-dasar#
# Install all dependencies from package.json
bun install
# Add a dependency
bun add express
# Add a dev dependency
bun add -d vitest
# Remove a dependency
bun remove express
# Update a dependency
bun update express
# Install a specific version
bun add express@4.18.2Jika Anda pernah menggunakan npm atau yarn, ini sepenuhnya familiar. Flag-nya sedikit berbeda (-d alih-alih --save-dev), tetapi model mentalnya identik.
Situasi Lockfile#
Bun menggunakan bun.lockb, lockfile biner. Ini adalah kekuatan supernya sekaligus titik gesekan terbesarnya.
Yang bagus: Jauh lebih cepat dibaca dan ditulis. Format biner berarti Bun bisa parsing lockfile dalam mikrodetik, bukan ratusan milidetik yang dihabiskan npm untuk parsing package-lock.json.
Yang buruk: Anda tidak bisa mereview-nya dalam diff. Jika Anda dalam sebuah tim dan seseorang memperbarui dependency, Anda tidak bisa melihat diff lockfile di PR dan melihat apa yang berubah. Ini lebih penting dari yang mau diakui para pendukung kecepatan.
# You can dump the lockfile to human-readable format
bun bun.lockb > lockfile-dump.txt
# Or use the built-in text output
bun install --yarn
# This generates a yarn.lock alongside bun.lockbPendekatan saya: saya commit bun.lockb ke repo dan juga menghasilkan yarn.lock atau package-lock.json sebagai fallback yang bisa dibaca. Sabuk dan suspender.
Dukungan Workspace#
Bun mendukung workspace bergaya npm/yarn:
{
"name": "my-monorepo",
"workspaces": [
"packages/*",
"apps/*"
]
}# Install dependencies for all workspaces
bun install
# Run a script in a specific workspace
bun run --filter packages/shared build
# Add a dependency to a specific workspace
bun add react --filter apps/webDukungan workspace sudah solid dan telah meningkat secara signifikan. Celah utama dibandingkan pnpm adalah resolusi dependency workspace Bun kurang ketat — ketatnya pnpm adalah fitur untuk monorepo karena menangkap phantom dependency.
Kompatibilitas dengan Proyek yang Sudah Ada#
Anda bisa memasukkan bun install ke hampir semua proyek Node.js yang sudah ada. Ia membaca package.json, menghormati .npmrc untuk konfigurasi registry, dan menangani peerDependencies dengan benar. Transisinya biasanya:
# Step 1: Delete existing lockfile and node_modules
rm -rf node_modules package-lock.json yarn.lock pnpm-lock.yaml
# Step 2: Install with Bun
bun install
# Step 3: Verify your app still works
bun run dev
# or: node dist/server.js (Bun package manager, Node runtime)Saya sudah melakukan ini pada selusin proyek dan tidak pernah ada masalah dengan package manager-nya sendiri. Satu-satunya gotcha adalah jika pipeline CI Anda secara spesifik mencari package-lock.json — Anda perlu memperbaruinya untuk menangani bun.lockb.
Kompatibilitas Node.js#
Ini adalah bagian di mana saya harus paling hati-hati, karena situasinya berubah setiap bulan. Per awal 2026, inilah gambaran jujurnya.
Yang Berhasil#
Sebagian besar paket npm berfungsi tanpa modifikasi. Bun mengimplementasikan sebagian besar modul bawaan Node.js:
// These all work as expected in Bun
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";Baik CommonJS maupun ESM berfungsi. require() dan import bisa berdampingan. TypeScript berjalan tanpa langkah kompilasi apa pun — Bun menghapus tipe saat parsing.
Framework yang berfungsi:
- Express — berfungsi, termasuk ekosistem middleware
- Fastify — berfungsi
- Hono — berfungsi (dan sangat cocok dengan Bun)
- Next.js — berfungsi dengan catatan (lebih lanjut di bawah)
- Prisma — berfungsi
- Drizzle ORM — berfungsi
- Socket.io — berfungsi
Yang Tidak Berfungsi (atau Bermasalah)#
Celahnya cenderung masuk ke beberapa kategori:
Addon native (node-gyp): Jika sebuah paket menggunakan addon C++ yang dikompilasi dengan node-gyp, ia mungkin tidak berfungsi dengan Bun. Bun punya sistem FFI sendiri dan mendukung banyak modul native, tetapi cakupannya belum 100%. Contohnya, bcrypt (yang native) pernah bermasalah — gunakan bcryptjs sebagai gantinya.
# Check if a package uses native addons
ls node_modules/your-package/binding.gyp # If this exists, it's nativeInternal Node.js tertentu: Beberapa paket mengakses internal Node.js seperti process.binding() atau menggunakan API spesifik V8. Ini tidak akan berfungsi di Bun karena ia berjalan di JavaScriptCore.
// This will NOT work in Bun — V8-specific
const v8 = require("v8");
v8.serialize({ data: "test" });
// This WILL work — use Bun's equivalent or a cross-runtime approach
const encoded = new TextEncoder().encode(JSON.stringify({ data: "test" }));Worker thread: Bun mendukung Web Worker dan node:worker_threads, tetapi ada edge case. Beberapa pola penggunaan tingkat lanjut — terutama seputar SharedArrayBuffer dan Atomics — bisa berperilaku berbeda.
Modul vm: node:vm memiliki dukungan parsial. Jika kode Anda atau dependency menggunakan vm.createContext() secara ekstensif (beberapa template engine melakukannya), uji secara menyeluruh.
Pelacak Kompatibilitas#
Bun memelihara pelacak kompatibilitas resmi. Periksa sebelum berkomitmen pada Bun untuk sebuah proyek:
# Run Bun's built-in compatibility check on your project
bun --bun node_modules/.bin/your-tool
# The --bun flag forces Bun's runtime even for node_modules scriptsRekomendasi saya: jangan berasumsi kompatibilitas. Jalankan test suite Anda di bawah Bun sebelum memutuskan. Butuh lima menit dan menghemat berjam-jam debugging.
# Quick compatibility check — run your full test suite under Bun
bun test # If you use bun test runner
# or
bun run vitest # If you use vitestAPI Bawaan Bun#
Di sinilah Bun menjadi menarik. Alih-alih hanya mengimplementasikan ulang API Node.js, Bun menyediakan API-nya sendiri yang dirancang untuk lebih sederhana dan lebih cepat.
Bun.serve() — HTTP Server Bawaan#
Ini adalah API yang paling sering saya gunakan. Bersih, cepat, dan dukungan WebSocket sudah terpasang langsung.
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}`);Beberapa hal yang perlu diperhatikan:
- Request/Response standar Web — bukan API proprietary. Handler
fetchmenerimaRequeststandar dan mengembalikanResponsestandar. Jika Anda pernah menulis Cloudflare Worker, ini terasa identik. Response.json()— helper respons JSON bawaan.- Tidak perlu import —
Bun.serveadalah global. Tidak perlurequire("http").
Berikut contoh yang lebih realistis dengan routing, parsing body JSON, dan penanganan error:
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}`);Itu adalah API CRUD lengkap dengan SQLite dalam sekitar 50 baris. Tanpa Express, tanpa ORM, tanpa rantai middleware. Untuk API kecil dan alat internal, ini adalah setup andalan saya sekarang.
Bun.file() dan Bun.write() — File I/O#
API file Bun sangat menyegarkan kesederhanaannya dibandingkan fs.readFile():
// Reading files
const file = Bun.file("./config.json");
const text = await file.text(); // Read as string
const json = await file.json(); // Parse as JSON directly
const bytes = await file.arrayBuffer(); // Read as ArrayBuffer
const stream = file.stream(); // Read as ReadableStream
// File metadata
console.log(file.size); // Size in bytes
console.log(file.type); // MIME type (e.g., "application/json")
// Writing files
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"));
// Write a Response body to a file
const response = await fetch("https://example.com/data.json");
await Bun.write("./downloaded.json", response);API Bun.file() bersifat lazy — ia tidak membaca file sampai Anda memanggil .text(), .json(), dsb. Artinya Anda bisa meneruskan referensi Bun.file() ke mana-mana tanpa menimbulkan biaya I/O sampai Anda benar-benar membutuhkan datanya.
Dukungan WebSocket Bawaan#
WebSocket adalah warga kelas satu di Bun.serve():
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) {
// Broadcast to all subscribers
server.publish("chat", `${ws.data.userId}: ${message}`);
},
close(ws) {
console.log(`Client disconnected: ${ws.data.userId}`);
ws.unsubscribe("chat");
},
},
});Pola server.publish() dan ws.subscribe() adalah pub/sub bawaan. Tanpa Redis, tanpa library WebSocket terpisah. Untuk fitur real-time sederhana, ini sangat nyaman.
SQLite Bawaan dengan bun:sqlite#
Ini yang paling mengejutkan saya. Bun sudah menyertakan SQLite langsung di dalam runtime-nya:
import { Database } from "bun:sqlite";
// Open or create a database
const db = new Database("myapp.db");
// WAL mode for better concurrent read performance
db.exec("PRAGMA journal_mode = WAL");
// Create tables
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 statements (reusable, faster for repeated queries)
const insertUser = db.prepare(
"INSERT INTO users (email, name) VALUES ($email, $name) RETURNING *"
);
const findByEmail = db.prepare(
"SELECT * FROM users WHERE email = $email"
);
// Usage
const user = insertUser.get({
$email: "alice@example.com",
$name: "Alice",
});
console.log(user); // { id: 1, email: "alice@example.com", name: "Alice", ... }
// Transactions
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`);Ini adalah SQLite sinkron dengan performa library C (karena memang demikian — Bun menyematkan libsqlite3 secara langsung). Untuk alat CLI, aplikasi local-first, dan layanan kecil, SQLite bawaan berarti nol dependency eksternal untuk lapisan data Anda.
Test Runner Bun#
bun test adalah pengganti drop-in untuk Jest dalam sebagian besar kasus. Ia menggunakan API describe/it/expect yang sama dan mendukung sebagian besar matcher Jest.
Penggunaan Dasar#
// 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);
});
});# Run all tests
bun test
# Run specific file
bun test math.test.ts
# Run tests matching a pattern
bun test --test-name-pattern "adds numbers"
# Watch mode
bun test --watch
# Coverage
bun test --coverageMocking#
Bun mendukung mocking yang kompatibel dengan Jest:
import { describe, it, expect, mock, spyOn } from "bun:test";
import { fetchUsers } from "./api";
// Mock a module
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");
});
});
// Spy on an object method
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 — Perbandingan Jujur Saya#
Saya menggunakan Vitest untuk proyek ini (dan sebagian besar proyek saya). Berikut mengapa saya belum sepenuhnya beralih:
Di mana bun test menang:
- Kecepatan startup.
bun testmulai mengeksekusi test lebih cepat dari Vitest yang bahkan belum selesai memuat konfigurasinya. - Nol konfigurasi. Tidak perlu
vitest.config.tsuntuk setup dasar. - TypeScript bawaan. Tidak ada langkah transformasi.
Di mana Vitest masih menang:
- Ekosistem: Vitest punya lebih banyak plugin, integrasi IDE lebih baik, dan komunitas lebih besar.
- Konfigurasi: Sistem konfigurasi Vitest lebih fleksibel. Reporter kustom, file setup yang kompleks, multiple test environment.
- Mode browser: Vitest bisa menjalankan test di browser sungguhan. Bun tidak bisa.
- Kompatibilitas: Beberapa library testing (Testing Library, MSW) sudah diuji lebih menyeluruh dengan Vitest/Jest.
- Snapshot testing: Keduanya mendukungnya, tetapi implementasi Vitest lebih matang dengan output diff yang lebih baik.
Untuk proyek baru dengan kebutuhan testing sederhana, saya akan menggunakan bun test. Untuk proyek yang sudah mapan dengan Testing Library, MSW, dan mocking yang kompleks, saya tetap menggunakan Vitest.
Bundler Bun#
bun build adalah bundler JavaScript/TypeScript yang cepat. Ia bukan pengganti webpack — ia lebih masuk kategori esbuild: cepat, opinionated, dan fokus pada kasus-kasus umum.
Bundling Dasar#
# Bundle a single entry point
bun build ./src/index.ts --outdir ./dist
# Bundle for different targets
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
# Generate sourcemaps
bun build ./src/index.ts --outdir ./dist --sourcemap externalAPI Programatik#
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, // Code splitting
sourcemap: "external",
external: ["react", "react-dom"], // Don't bundle these
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 mendukung tree-shaking untuk ESM:
// 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
# The `unused` function won't appear in the outputDi Mana Bun Build Kurang#
- Tidak ada bundling CSS — Anda butuh alat terpisah untuk CSS (PostCSS, Lightning CSS, Tailwind CLI).
- Tidak ada generasi HTML — Ia membundle JavaScript/TypeScript, bukan aplikasi web lengkap.
- Ekosistem plugin — esbuild punya ekosistem plugin yang jauh lebih besar. API plugin Bun kompatibel tetapi komunitasnya lebih kecil.
- Code splitting tingkat lanjut — Webpack dan Rollup masih menawarkan strategi chunk yang lebih canggih.
Untuk membangun library atau bundle JS aplikasi web sederhana, bun build sangat baik. Untuk build aplikasi kompleks dengan modul CSS, optimisasi gambar, dan strategi chunk kustom, Anda masih membutuhkan bundler yang lengkap.
Makro Bun#
Satu fitur yang benar-benar unik: eksekusi kode saat kompilasi melalui makro.
// build-info.ts — this file runs at BUILD TIME, not runtime
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() executes at bundle time
// The result is inlined as a static value
const info = getBuildInfo();
console.log(`Built at ${info.builtAt}, commit ${info.gitSha}`);Setelah bundling, getBuildInfo() diganti dengan objek literal — tidak ada pemanggilan fungsi saat runtime, tidak ada import child_process. Kodenya berjalan saat build dan hasilnya di-inline. Ini sangat powerful untuk menyematkan metadata build, feature flag, atau konfigurasi khusus environment.
Menggunakan Bun dengan Next.js#
Ini adalah pertanyaan yang paling sering saya terima, jadi izinkan saya sangat spesifik.
Apa yang Berfungsi Saat Ini#
Bun sebagai package manager untuk Next.js — berfungsi sempurna:
# Use Bun to install dependencies, then use Node.js to run Next.js
bun install
bun run dev # This actually runs the "dev" script via Node.js by default
bun run build
bun run startInilah yang saya lakukan untuk setiap proyek Next.js. Perintah bun run <script> membaca bagian scripts dari package.json dan mengeksekusinya. Secara default, ia menggunakan Node.js sistem untuk eksekusi sebenarnya. Anda mendapat instalasi paket Bun yang cepat tanpa mengubah runtime Anda.
Runtime Bun untuk pengembangan Next.js:
# Force Next.js to run under Bun's runtime
bun --bun run devIni berfungsi untuk pengembangan dalam sebagian besar kasus. Flag --bun memberi tahu Bun untuk menggunakan runtime-nya sendiri alih-alih mendelegasikan ke Node.js. Hot module replacement berfungsi. API route berfungsi. Server component berfungsi.
Yang Masih Eksperimental#
Runtime Bun untuk build produksi Next.js:
# Build with Bun runtime
bun --bun run build
# Start production server with Bun runtime
bun --bun run startIni berfungsi untuk banyak proyek tetapi saya menemukan edge case:
- Beberapa perilaku middleware berbeda — Jika Anda menggunakan middleware Next.js yang bergantung pada API spesifik Node.js, Anda mungkin mengalami masalah kompatibilitas.
- Optimisasi gambar — Pipeline optimisasi gambar Next.js menggunakan sharp, yang merupakan addon native. Ia berfungsi dengan Bun, tetapi saya pernah melihat masalah sesekali.
- ISR (Incremental Static Regeneration) — Berfungsi, tetapi saya belum mengujinya secara intensif di bawah Bun di produksi.
Rekomendasi Saya untuk Next.js#
Gunakan Bun sebagai package manager. Gunakan Node.js sebagai runtime. Ini memberikan Anda manfaat kecepatan dari bun install tanpa risiko kompatibilitas apa pun.
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start"
}
}# Daily workflow
bun install # Fast package installation
bun run dev # Runs "next dev" via Node.js
bun run build # Runs "next build" via Node.jsKetika kompatibilitas Bun dengan Node.js mencapai 100% untuk penggunaan internal Next.js (sudah dekat, tapi belum sampai), saya akan beralih. Sampai saat itu, package manager saja sudah cukup menghemat waktu saya untuk membenarkan instalasi ini.
Docker dengan Bun#
Image Docker Bun yang resmi dipelihara dengan baik dan siap produksi.
Dockerfile Dasar#
FROM oven/bun:1 AS base
WORKDIR /app
# Install dependencies
FROM base AS deps
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
# Build (if needed)
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
# Don't run as root
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"]Build Multi-Stage untuk Image Minimal#
# Build stage: full Bun image with all dependencies
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 stage: smaller 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"]Mengompilasi ke Binary Tunggal#
Ini adalah salah satu fitur killer Bun untuk deployment:
# Compile your app into a single executable
bun build --compile ./src/server.ts --outfile server
# The output is a standalone binary — no Bun or Node.js needed to run it
./server# Ultra-minimal Docker image using compiled binary
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 — just the 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"]Binary yang dikompilasi biasanya 50-90 MB (ia membundle runtime Bun). Itu lebih besar dari binary Go tetapi jauh lebih kecil dari instalasi Node.js penuh plus node_modules. Untuk deployment dalam container, sifat self-contained ini adalah penyederhanaan yang signifikan.
Perbandingan Ukuran#
# Node.js image
docker images | grep node
# node:20-slim ~180MB
# Bun image
docker images | grep bun
# oven/bun:1-slim ~130MB
# Compiled binary on debian:bookworm-slim
# ~80MB base + ~70MB binary = ~150MB total
# vs. Alpine with Node.js
# node:20-alpine ~130MB + node_modulesPendekatan binary menghilangkan node_modules sepenuhnya dari image akhir. Tidak ada npm install di produksi. Tidak ada surface area supply chain dari ratusan paket. Hanya satu file.
Pola Migrasi#
Jika Anda mempertimbangkan untuk pindah ke Bun, berikut jalur inkremental yang saya rekomendasikan:
Fase 1: Hanya Package Manager (Risiko Nol)#
# Replace npm/yarn/pnpm with bun install
# Change your CI pipeline:
# Before:
npm ci
# After:
bun install --frozen-lockfileTidak ada perubahan kode. Tidak ada perubahan runtime. Hanya instalasi yang lebih cepat. Jika ada yang rusak (tidak akan), kembalikan dengan menghapus bun.lockb dan menjalankan npm install.
Fase 2: Skrip dan Tooling#
# Use bun to run development scripts
bun run dev
bun run lint
bun run format
# Use bun for one-off scripts
bun run scripts/seed-database.ts
bun run scripts/migrate.tsMasih menggunakan Node.js sebagai runtime untuk aplikasi sesungguhnya. Tetapi skrip mendapat manfaat dari startup Bun yang lebih cepat dan dukungan TypeScript native.
Fase 3: Test Runner (Risiko Menengah)#
# Replace vitest/jest with bun test for simple test suites
bun test
# Keep vitest for complex test setups
# (Testing Library, MSW, custom environments)Jalankan seluruh test suite Anda di bawah bun test. Jika semuanya lolos, Anda telah menghilangkan satu devDependency. Jika beberapa test gagal karena kompatibilitas, tetap gunakan Vitest untuk test tersebut dan gunakan bun test untuk sisanya.
Fase 4: Runtime untuk Layanan Baru (Risiko Terkalkukasi)#
// New microservices or APIs — start with Bun from day one
Bun.serve({
port: 3000,
fetch(req) {
// Your new service here
},
});Jangan migrasikan layanan Node.js yang sudah ada ke runtime Bun. Sebagai gantinya, tulis layanan baru dengan Bun dari awal. Ini membatasi blast radius Anda.
Fase 5: Migrasi Runtime (Lanjutan)#
# Only after thorough testing:
# Replace node with bun for existing services
# Before:
node dist/server.js
# After:
bun dist/server.jsSaya hanya merekomendasikan ini untuk layanan dengan cakupan test yang sangat baik. Jalankan load test Anda di bawah Bun sebelum beralih ke produksi.
Variabel Lingkungan dan Konfigurasi#
Bun menangani file .env secara otomatis — tidak perlu paket dotenv:
# .env
DATABASE_URL=postgresql://localhost:5432/myapp
API_KEY=sk-test-12345
PORT=3000// These are available without any import
console.log(process.env.DATABASE_URL);
console.log(process.env.API_KEY);
console.log(Bun.env.PORT); // Bun-specific alternativeBun memuat .env, .env.local, .env.production, dsb. secara otomatis, mengikuti konvensi yang sama seperti Next.js. Satu dependency lebih sedikit di package.json Anda.
Penanganan Error dan Debugging#
Output error Bun telah meningkat secara signifikan, tetapi masih belum se-polish Node.js dalam beberapa kasus:
# Bun's debugger — works with VS Code
bun --inspect run server.ts
# Bun's inspect-brk — pause on first line
bun --inspect-brk run server.tsUntuk VS Code, tambahkan ini ke .vscode/launch.json Anda:
{
"version": "0.2.0",
"configurations": [
{
"type": "bun",
"request": "launch",
"name": "Debug Bun",
"program": "${workspaceFolder}/src/server.ts",
"cwd": "${workspaceFolder}",
"stopOnEntry": false,
"watchMode": false
}
]
}Stack trace di Bun umumnya akurat dan menyertakan source map untuk TypeScript. Celah debugging utama adalah beberapa alat debugging spesifik Node.js (seperti ndb atau clinic.js) tidak berfungsi dengan Bun.
Pertimbangan Keamanan#
Beberapa hal yang perlu dipikirkan jika Anda sedang mengevaluasi Bun untuk produksi:
Kematangan: Node.js sudah di produksi selama 15+ tahun. Setiap edge case dalam parsing HTTP, penanganan TLS, dan pemrosesan stream sudah ditemukan dan diperbaiki. Bun lebih muda. Ia sudah diuji dengan baik, tetapi area permukaan untuk bug yang belum ditemukan lebih besar.
Patch keamanan: Tim Bun merilis pembaruan secara sering, tetapi tim keamanan Node.js memiliki proses CVE formal, pengungkapan terkoordinasi, dan rekam jejak yang lebih panjang. Untuk aplikasi yang kritis terhadap keamanan, ini penting.
Supply chain: Fitur bawaan Bun (SQLite, HTTP server, WebSocket) berarti lebih sedikit dependency npm. Lebih sedikit dependency berarti permukaan serangan supply chain yang lebih kecil. Ini adalah keunggulan keamanan yang sesungguhnya.
# Compare dependency counts
# A typical Express + SQLite + WebSocket project:
npm ls --all | wc -l
# ~340 packages
# The same functionality with Bun built-ins:
bun pm ls --all | wc -l
# ~12 packages (just your application code)Itu adalah pengurangan yang bermakna dalam jumlah paket yang Anda percayai untuk beban kerja produksi Anda.
Tuning Performa#
Beberapa tips performa khusus Bun:
// Use Bun.serve() options for production tuning
Bun.serve({
port: 3000,
// Increase max request body size (default is 128MB)
maxRequestBodySize: 1024 * 1024 * 50, // 50MB
// Enable development mode for better error pages
development: process.env.NODE_ENV !== "production",
// Reuse port (useful for zero-downtime restarts)
reusePort: true,
fetch(req) {
return new Response("OK");
},
});// Use Bun.Transpiler for runtime code transformation
const transpiler = new Bun.Transpiler({
loader: "tsx",
target: "browser",
});
const code = transpiler.transformSync(`
const App: React.FC = () => <div>Hello</div>;
export default App;
`);# Bun's memory usage flags
bun --smol run server.ts # Reduce memory footprint (slightly slower)
# Set max heap size
BUN_JSC_forceRAMSize=512000000 bun run server.ts # ~512MB limitGotcha Umum#
Setelah setahun menggunakan Bun, berikut hal-hal yang pernah menjebak saya:
1. Perilaku Global Fetch Berbeda#
// Node.js 18+ fetch and Bun's fetch are slightly different
// in how they handle certain headers and redirects
// Bun follows redirects by default (like browsers)
// Node.js fetch also follows redirects, but the behavior
// with certain status codes (303, 307, 308) can differ
const response = await fetch("https://api.example.com/data", {
redirect: "manual", // Be explicit about redirect handling
});2. Perilaku Process Exit#
// Bun exits when the event loop is empty
// Node.js sometimes keeps running due to lingering handles
// If your Bun script exits unexpectedly, something isn't
// keeping the event loop alive
// This will exit immediately in Bun:
setTimeout(() => {}, 0);
// This will keep running:
setTimeout(() => {}, 1000);
// (Bun exits after the timeout fires)3. Konfigurasi TypeScript#
// Bun has its own tsconfig defaults
// If you're sharing a project between Bun and Node.js,
// be explicit in your tsconfig.json:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["bun-types"] // Add Bun type definitions
}
}# Install Bun types
bun add -d @types/bun4. Hot Reload di Pengembangan#
# Bun has built-in watch mode
bun --watch run server.ts
# This restarts the process on file changes
# It's not HMR (Hot Module Replacement) — it's a full restart
# But because Bun starts so fast, it feels instant5. File Konfigurasi bunfig.toml#
# bunfig.toml — Bun's config file (optional)
[install]
# Use a private registry
registry = "https://npm.mycompany.com"
# Scoped registries
[install.scopes]
"@mycompany" = "https://npm.mycompany.com"
[test]
# Test configuration
coverage = true
coverageReporter = ["text", "lcov"]
[run]
# Shell to use for bun run
shell = "bash"Putusan Saya#
Setelah setahun penggunaan produksi, inilah di mana saya menetap:
Di Mana Saya Menggunakan Bun Saat Ini#
Package manager untuk semua proyek — termasuk blog Next.js ini. bun install lebih cepat, dan kompatibilitasnya pada dasarnya sempurna. Saya tidak melihat alasan untuk menggunakan npm atau yarn lagi. pnpm adalah satu-satunya alternatif yang akan saya pertimbangkan (untuk resolusi dependency yang ketat di monorepo).
Runtime untuk skrip dan alat CLI — File TypeScript apa pun yang perlu saya jalankan sekali, saya jalankan dengan bun. Tanpa langkah kompilasi. Startup cepat. Loading .env bawaan. Ini telah menggantikan ts-node dan tsx dalam workflow saya sepenuhnya.
Runtime untuk API kecil dan alat internal — Bun.serve() + bun:sqlite adalah stack yang sangat produktif untuk alat internal, webhook handler, dan layanan kecil. Model deployment "satu binary, tanpa dependency" sangat meyakinkan.
Test runner untuk proyek sederhana — Untuk proyek dengan kebutuhan testing yang sederhana, bun test cepat dan tidak memerlukan konfigurasi apa pun.
Di Mana Saya Tetap dengan Node.js#
Next.js produksi — Bukan karena Bun tidak berfungsi, tetapi karena risiko-manfaatnya belum membenarkan hal tersebut. Next.js adalah framework yang kompleks dengan banyak titik integrasi. Saya ingin runtime yang paling teruji di bawahnya.
Layanan produksi kritis — Server API utama saya berjalan di Node.js di belakang PM2. Ekosistem monitoring, alat debugging, pengetahuan operasional — semuanya Node.js. Bun akan sampai di sana, tetapi belum saat ini.
Apa pun dengan addon native — Jika rantai dependency menyertakan addon native C++, saya bahkan tidak mencoba Bun. Tidak sepadan debugging masalah kompatibilitasnya.
Tim yang belum familiar dengan Bun — Memperkenalkan Bun sebagai runtime ke tim yang belum pernah menggunakannya menambah beban kognitif. Sebagai package manager, tidak masalah. Sebagai runtime, tunggu sampai tim siap.
Yang Saya Pantau#
Pelacak kompatibilitas Bun — Ketika mencapai 100% untuk API Node.js yang saya pedulikan, saya akan menilai ulang.
Dukungan framework — Next.js, Remix, dan SvelteKit semuanya memiliki tingkat dukungan Bun yang bervariasi. Ketika salah satu dari mereka secara resmi mendukung Bun sebagai runtime produksi, itu adalah sinyal.
Adopsi enterprise — Begitu perusahaan dengan SLA nyata menjalankan Bun di produksi dan menulis tentangnya, pertanyaan kematangan terjawab.
Lini rilis 1.2+ — Bun bergerak cepat. Fitur mendarat setiap minggu. Bun yang saya gunakan hari ini jauh lebih baik dari Bun yang saya coba setahun lalu.
Penutup#
Bun bukan peluru perak. Ia tidak akan membuat aplikasi lambat menjadi cepat dan tidak akan membuat API yang dirancang buruk menjadi dirancang dengan baik. Tetapi ia adalah peningkatan nyata dalam pengalaman developer untuk ekosistem JavaScript.
Hal yang paling saya hargai dari Bun bukan fitur tunggal mana pun. Ini adalah pengurangan kompleksitas toolchain. Satu binary yang menginstal paket, menjalankan TypeScript, membundle kode, dan menjalankan test. Tidak perlu tsconfig.json untuk skrip. Tidak ada Babel. Tidak ada konfigurasi test runner terpisah. Cukup bun run your-file.ts dan berfungsi.
Saran praktisnya: mulai dengan bun install. Risikonya nol, manfaatnya langsung. Lalu coba bun run untuk skrip. Kemudian evaluasi sisanya berdasarkan kebutuhan spesifik Anda. Anda tidak harus all-in. Bun berfungsi dengan sempurna sebagai pengganti parsial, dan itulah mungkin cara kebanyakan orang seharusnya menggunakannya saat ini.
Lanskap runtime JavaScript lebih baik dengan Bun di dalamnya. Kompetisi membuat Node.js lebih baik juga — Node.js 22+ sudah menjadi jauh lebih cepat, sebagian sebagai respons terhadap tekanan dari Bun. Semua orang menang.