Bun v produkci: Co funguje, co ne a co mě překvapilo
Bun jako runtime, package manager, bundler a test runner. Reálné benchmarky, mezery v kompatibilitě s Node.js, migrační vzory a kde Bun používám v produkci dnes.
Každých pár let se v ekosystému JavaScriptu objeví nový runtime a diskuze sleduje předvídatelný oblouk. Hype. Benchmarky. „X je mrtvý." Kontrola reality. Usazení se v reálných případech použití, kde nový nástroj skutečně září.
Bun je právě uprostřed tohoto oblouku. A na rozdíl od většiny vyzyvatelů tu zůstává. Ne proto, že je „rychlejší" (i když často je), ale protože řeší skutečně jiný problém: JavaScriptový toolchain má příliš mnoho pohyblivých částí a Bun je sbalí do jednoho.
Bun používám v různých kapacitách už přes rok. Částečně v produkci. Částečně jako náhradu nástrojů, o kterých jsem si myslel, že je nikdy nenahradím. Tento příspěvek je poctivé zhodnocení toho, co funguje, co ne a kde mezery stále hrají roli.
Co Bun vlastně je#
První mylná představa, kterou je třeba vyvrátit: Bun není „rychlejší Node.js." Takové zarámování ho podhodnocuje.
Bun jsou čtyři nástroje v jednom binárním souboru:
- JavaScript/TypeScript runtime — spouští váš kód, jako Node.js nebo Deno
- Package manager — nahrazuje npm, yarn nebo pnpm
- Bundler — nahrazuje esbuild, webpack nebo Rollup pro určité případy použití
- Test runner — nahrazuje Jest nebo Vitest pro většinu testovacích sad
Klíčový architektonický rozdíl oproti Node.js je engine. Node.js používá V8 (engine Chrome). Bun používá JavaScriptCore (engine Safari). Oba jsou vyspělé, produkčně ověřené enginy, ale dělají různé kompromisy. JavaScriptCore má tendenci k rychlejším startovacím časům a nižší paměťové režii. V8 má tendenci k lepší špičkové propustnosti pro dlouhodobé výpočty. V praxi jsou tyto rozdíly menší, než byste čekali, pro většinu úloh.
Další hlavní odlišnost: Bun je napsaný v Zig, systémovém programovacím jazyce, který je zhruba na stejné úrovni jako C, ale s lepšími zárukami paměťové bezpečnosti. Proto si Bun může dovolit být tak agresivní s výkonem — Zig vám dává takovou nízkoúrovňovou kontrolu, jakou poskytuje C, bez hustoty nebezpečných chyb, které C přináší.
# 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 ./distTo je jeden binární soubor, který dělá práci node + npm + esbuild + vitest. Milujte to nebo nenáviďte, je to přesvědčivé zjednodušení.
Tvrzení o rychlosti — poctivé benchmarky#
Řeknu to přímo: marketingové benchmarky Bunu jsou cherry-picked. Ne podvodné — cherry-picked. Ukazují scénáře, kde Bun funguje nejlépe, což je přesně to, co byste očekávali od marketingových materiálů. Problém je, že lidé z těchto benchmarků extrapolují a tvrdí, že Bun je „25x rychlejší" ve všem, což rozhodně není.
Zde je Bun skutečně, měřitelně rychlejší:
Startovací čas#
Toto je největší skutečná výhoda Bunu a není to ani blízko.
# 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: ~6msTo je zhruba 6-7násobný rozdíl ve startovacím čase. Pro skripty, CLI nástroje a serverless funkce, kde záleží na studeném startu, je to významné. Pro dlouhodobě běžící serverový proces, který se spustí jednou a běží týdny, je to irelevantní.
Instalace balíčků#
To je další oblast, kde Bun zahanbuje konkurenci.
# 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.1sTo je 8-9násobný rozdíl a je konzistentní. Důvody jsou především:
- Binární lockfile —
bun.lockbje binární formát, ne JSON. Rychlejší čtení i zápis. - Globální cache — Bun udržuje globální cache modulů, takže reinstalace napříč projekty sdílejí stažené balíčky.
- I/O v Zig — Package manager sám o sobě je napsaný v Zig, ne v JavaScriptu. Souborové I/O operace jsou blíže kovu.
- Strategie symlinků — Bun používá hardlinky z globální cache místo kopírování souborů.
Propustnost HTTP serveru#
Vestavěný HTTP server Bunu je rychlý, ale srovnání potřebují kontext.
# 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. surový Node.js: zhruba 2x pro triviální odpovědi. Bun vs. Express: zhruba 7x, ale to je neférové, protože Express přidává režii middleware. Jakmile přidáte skutečnou logiku — databázové dotazy, autentizaci, JSON serializaci reálných dat — rozdíl se dramaticky zmenšuje.
Kde je rozdíl zanedbatelný#
CPU-náročné výpočty:
// 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) zde ve skutečnosti mírně vyhrává. JIT kompilátor V8 je agresivnější na hot loops. Pro CPU-náročnou práci jsou rozdíly mezi enginy vyrovnané — někdy vyhraje V8, někdy JSC a rozdíly jsou v rámci šumu.
Jak spustit vlastní benchmarky#
Nevěřte benchmarkům nikoho, ani mým. Zde je návod, jak měřit to, co je důležité pro vaši konkrétní zátěž:
# 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 # macOSObecné pravidlo: pokud je vaše úzké hrdlo I/O (souborový systém, síť, databáze), výhoda Bunu je skromná. Pokud je vaše úzké hrdlo startovací čas nebo rychlost toolchainu, Bun jasně vyhrává. Pokud je vaše úzké hrdlo surový výpočet, je to nerozhodně.
Bun jako package manager#
Toto je oblast, kde jsem kompletně přešel. I v projektech, kde v produkci běží Node.js, používám bun install pro lokální vývoj a CI. Je to prostě rychlejší a kompatibilita je vynikající.
Základy#
# 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.2Pokud jste používali npm nebo yarn, je to zcela známé. Flagy se mírně liší (-d místo --save-dev), ale mentální model je identický.
Situace s lockfilem#
Bun používá bun.lockb, binární lockfile. To je jak jeho superschopnost, tak jeho největší bod tření.
Pozitivum: Je dramaticky rychlejší na čtení i zápis. Binární formát znamená, že Bun dokáže zparsovat lockfile v mikrosekundách, ne ve stovkách milisekund, které npm stráví parsováním package-lock.json.
Negativum: Nemůžete ho zkontrolovat v diffu. Pokud jste v týmu a někdo aktualizuje závislost, nemůžete se v PR podívat na diff lockfilu a vidět, co se změnilo. To je důležitější, než zastánci rychlosti chtějí přiznat.
# 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.lockbMůj přístup: commituju bun.lockb do repozitáře a také generuji yarn.lock nebo package-lock.json jako čitelný fallback. Opasek a šle.
Podpora workspace#
Bun podporuje npm/yarn-style workspace:
{
"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/webPodpora workspace je solidní a výrazně se zlepšila. Hlavní mezera oproti pnpm je, že rozlišování závislostí v workspace Bunu je méně přísné — přísnost pnpm je vlastnost pro monorepa, protože zachytí fantomové závislosti.
Kompatibilita s existujícími projekty#
Můžete nasadit bun install téměř do jakéhokoli existujícího Node.js projektu. Čte package.json, respektuje .npmrc pro konfiguraci registru a správně zpracovává peerDependencies. Přechod je typicky:
# 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)Udělal jsem to na tuctu projektů a neměl jsem žádné problémy s package managerem samotným. Jediný háček je, pokud vaše CI pipeline specificky hledá package-lock.json — budete muset upravit, aby zvládla bun.lockb.
Kompatibilita s Node.js#
Toto je sekce, kde musím být nejopatrnější, protože situace se mění každý měsíc. K začátku roku 2026 je zde poctivý obrázek.
Co funguje#
Drtivá většina npm balíčků funguje bez úprav. Bun implementuje většinu vestavěných modulů 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";Funguje jak CommonJS, tak ESM. require() a import mohou koexistovat. TypeScript běží bez jakéhokoli kompilačního kroku — Bun odstraňuje typy při parsování.
Frameworky, které fungují:
- Express — funguje, včetně ekosystému middleware
- Fastify — funguje
- Hono — funguje (a je vynikající s Bun)
- Next.js — funguje s výhradami (více níže)
- Prisma — funguje
- Drizzle ORM — funguje
- Socket.io — funguje
Co nefunguje (nebo má problémy)#
Mezery mají tendenci spadat do několika kategorií:
Nativní addony (node-gyp): Pokud balíček používá C++ addony zkompilované s node-gyp, nemusí s Bun fungovat. Bun má vlastní FFI systém a podporuje mnoho nativních modulů, ale pokrytí není 100%. Například bcrypt (nativní verze) měl problémy — použijte místo toho bcryptjs.
# Check if a package uses native addons
ls node_modules/your-package/binding.gyp # If this exists, it's nativeSpecifické interní části Node.js: Některé balíčky sahají do interních částí Node.js jako process.binding() nebo používají V8-specifická API. Ty v Bun nebudou fungovat, protože běží na 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 threads: Bun podporuje Web Workers a node:worker_threads, ale existují okrajové případy. Některé pokročilé vzory použití — zejména kolem SharedArrayBuffer a Atomics — se mohou chovat odlišně.
Modul vm: node:vm má částečnou podporu. Pokud váš kód nebo závislost intenzivně používá vm.createContext() (některé šablonovací enginy to dělají), důkladně otestujte.
Sledování kompatibility#
Bun udržuje oficiální sledování kompatibility. Zkontrolujte ho, než se zavážete k Bun pro projekt:
# 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 scriptsMé doporučení: nepředpokládejte kompatibilitu. Spusťte svou testovací sadu pod Bun, než se rozhodnete. Zabere to pět minut a ušetří hodiny ladění.
# 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 vitestVestavěná API Bunu#
Toto je místo, kde se Bun stává zajímavým. Místo pouhé reimplementace API Node.js poskytuje Bun vlastní API, která jsou navržena tak, aby byla jednodušší a rychlejší.
Bun.serve() — vestavěný HTTP server#
Toto je API, které používám nejvíce. Je čisté, rychlé a podpora WebSocket je zabudovaná přímo.
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}`);Pár věcí, kterých si všimnout:
- Webově-standardní Request/Response — žádné proprietární API. Handler
fetchpřijímá standardníRequesta vrací standardníResponse. Pokud jste psali Cloudflare Worker, tohle vypadá identicky. Response.json()— vestavěný helper pro JSON odpovědi.- Žádný import není potřeba —
Bun.serveje globální. Žádnýrequire("http").
Zde je realističtější příklad s routingem, parsováním JSON těla a zpracováním chyb:
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}`);To je kompletní CRUD API s SQLite na zhruba 50 řádcích. Žádný Express, žádný ORM, žádný řetězec middleware. Pro malá API a interní nástroje je to teď mé oblíbené nastavení.
Bun.file() a Bun.write() — souborové I/O#
API pro soubory v Bun je osvěžujícně jednoduché ve srovnání s 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() je líné — soubor nečte, dokud nezavoláte .text(), .json() atd. To znamená, že můžete předávat reference Bun.file() bez I/O nákladů, dokud data skutečně nepotřebujete.
Vestavěná podpora WebSocket#
WebSockety jsou prvotřídní v 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");
},
},
});Vzor server.publish() a ws.subscribe() je vestavěný pub/sub. Žádný Redis, žádná separátní WebSocket knihovna. Pro jednoduché real-time funkce je to neuvěřitelně pohodlné.
Vestavěný SQLite s bun:sqlite#
Tohle mě překvapilo nejvíce. Bun dodává SQLite zabudovaný přímo do runtime:
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`);Toto je synchronní SQLite s výkonem C knihovny (protože to jedna je — Bun přímo obsahuje libsqlite3). Pro CLI nástroje, local-first aplikace a malé služby vestavěný SQLite znamená nulové externí závislosti pro vaši datovou vrstvu.
Test runner Bunu#
bun test je drop-in náhrada za Jest ve většině případů. Používá stejné API describe/it/expect a podporuje většinu Jest matcherů.
Základní použití#
// 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 --coverageMockování#
Bun podporuje Jest-kompatibilní mockování:
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 — mé poctivé srovnání#
Vitest používám pro tento projekt (a většinu svých projektů). Zde je důvod, proč jsem kompletně nepřešel:
Kde bun test vyhrává:
- Rychlost startu.
bun testzačne vykonávat testy dříve, než Vitest stihne dokončit načítání své konfigurace. - Nulová konfigurace. Žádný
vitest.config.tspotřeba pro základní nastavení. - Vestavěný TypeScript. Žádný transformační krok.
Kde stále vyhrává Vitest:
- Ekosystém: Vitest má více pluginů, lepší integraci s IDE a větší komunitu.
- Konfigurace: Konfigurační systém Vitestu je flexibilnější. Vlastní reportéry, složité setup soubory, více testovacích prostředí.
- Režim prohlížeče: Vitest umí spouštět testy ve skutečném prohlížeči. Bun ne.
- Kompatibilita: Některé testovací knihovny (Testing Library, MSW) byly důkladněji otestovány s Vitest/Jest.
- Snapshot testování: Oba to podporují, ale implementace Vitestu je vyspělejší s lepším výstupem diffů.
Pro nový projekt s jednoduchými testovacími potřebami bych použil bun test. Pro zavedený projekt s Testing Library, MSW a složitým mockováním zůstávám u Vitestu.
Bundler Bunu#
bun build je rychlý JavaScript/TypeScript bundler. Není to náhrada webpacku — je to spíše v kategorii esbuildu: rychlý, mající jasné názory a zaměřený na běžné případy.
Základní bundlování#
# 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 externalProgramatické 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, // 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 podporuje tree-shaking pro 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 outputKde Bun build zaostává#
- Žádné CSS bundlování — Potřebujete separátní nástroj pro CSS (PostCSS, Lightning CSS, Tailwind CLI).
- Žádné generování HTML — Bundluje JavaScript/TypeScript, ne celé webové aplikace.
- Ekosystém pluginů — esbuild má mnohem větší ekosystém pluginů. API pluginů Bunu je kompatibilní, ale komunita je menší.
- Pokročilé dělení kódu — Webpack a Rollup stále nabízejí sofistikovanější strategie chunků.
Pro sestavení knihovny nebo jednoduchého JS bundlu webové aplikace je bun build vynikající. Pro komplexní sestavení aplikací s CSS moduly, optimalizací obrázků a vlastními strategiemi chunků budete stále chtít plný bundler.
Bun makra#
Jedna skutečně unikátní funkce: spouštění kódu v době kompilace pomocí maker.
// 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}`);Po bundlování je getBuildInfo() nahrazen literálním objektem — žádné volání funkce za běhu, žádný import child_process. Kód se spustil během sestavení a výsledek byl vložen inline. To je mocné pro vkládání metadat buildu, feature flagů nebo konfigurace specifické pro prostředí.
Použití Bunu s Next.js#
Toto je otázka, kterou dostávám nejčastěji, takže budu velmi konkrétní.
Co funguje dnes#
Bun jako package manager pro Next.js — funguje perfektně:
# 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 startToto dělám u každého Next.js projektu. Příkaz bun run <script> čte sekci scripts v package.json a spouští ji. Ve výchozím nastavení používá systémový Node.js pro skutečné spuštění. Získáte rychlou instalaci balíčků Bunu bez změny svého runtime.
Bun runtime pro Next.js vývoj:
# Force Next.js to run under Bun's runtime
bun --bun run devToto funguje pro vývoj ve většině případů. Flag --bun říká Bunu, aby použil svůj vlastní runtime místo delegování na Node.js. Hot module replacement funguje. API routes fungují. Server components fungují.
Co je stále experimentální#
Bun runtime pro produkční buildy Next.js:
# Build with Bun runtime
bun --bun run build
# Start production server with Bun runtime
bun --bun run startToto funguje pro mnoho projektů, ale narazil jsem na okrajové případy:
- Některé chování middleware se liší — Pokud používáte middleware Next.js, které závisí na Node.js-specifických API, můžete narazit na problémy s kompatibilitou.
- Optimalizace obrázků — Pipeline optimalizace obrázků Next.js používá sharp, což je nativní addon. Funguje s Bun, ale viděl jsem občasné problémy.
- ISR (Incremental Static Regeneration) — Funguje, ale netestoval jsem to pod Bun v produkci pod zátěží.
Mé doporučení pro Next.js#
Použijte Bun jako package manager. Použijte Node.js jako runtime. To vám dá rychlostní výhody bun install bez jakéhokoli rizika kompatibility.
{
"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.jsAž kompatibilita Bunu s Node.js dosáhne 100 % pro interní použití Next.js (je to blízko, ale ještě tam není), přehodnotím to. Do té doby mi samotný package manager ušetří dost času, aby se instalace vyplatila.
Docker s Bun#
Oficiální Docker image Bunu je dobře udržovaný a připravený pro produkci.
Základní Dockerfile#
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"]Multi-stage build pro minimální image#
# 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"]Kompilace do jednoho binárního souboru#
Toto je jedna z klíčových funkcí Bunu pro nasazení:
# 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"]Zkompilovaný binární soubor má typicky 50-90 MB (obsahuje Bun runtime). To je více než Go binárka, ale mnohem méně než plná instalace Node.js plus node_modules. Pro kontejnerizovaná nasazení je soběstačná povaha výrazným zjednodušením.
Porovnání velikostí#
# 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_modulesPřístup s binárkou kompletně eliminuje node_modules z finálního image. Žádný npm install v produkci. Žádný prostor pro útoky na dodavatelský řetězec ze stovek balíčků. Jen jeden soubor.
Migrační vzory#
Pokud zvažujete přechod na Bun, zde je inkrementální cesta, kterou doporučuji:
Fáze 1: Pouze package manager (nulové riziko)#
# Replace npm/yarn/pnpm with bun install
# Change your CI pipeline:
# Before:
npm ci
# After:
bun install --frozen-lockfileŽádné změny kódu. Žádné změny runtime. Jen rychlejší instalace. Pokud se něco pokazí (nestane se), vraťte se smazáním bun.lockb a spuštěním npm install.
Fáze 2: Skripty a nástroje#
# 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.tsStále používáte Node.js jako runtime pro vaši skutečnou aplikaci. Ale skripty profitují z rychlejšího startu Bunu a nativní podpory TypeScriptu.
Fáze 3: Test runner (střední riziko)#
# Replace vitest/jest with bun test for simple test suites
bun test
# Keep vitest for complex test setups
# (Testing Library, MSW, custom environments)Spusťte celou testovací sadu pod bun test. Pokud vše projde, eliminovali jste jednu devDependency. Pokud některé testy selžou kvůli kompatibilitě, ponechte Vitest pro ty a pro zbytek použijte bun test.
Fáze 4: Runtime pro nové služby (kalkulované riziko)#
// New microservices or APIs — start with Bun from day one
Bun.serve({
port: 3000,
fetch(req) {
// Your new service here
},
});Nemigrujte existující služby Node.js na Bun runtime. Místo toho pište nové služby s Bun od začátku. Tím omezíte svůj blast radius.
Fáze 5: Migrace runtime (pokročilé)#
# Only after thorough testing:
# Replace node with bun for existing services
# Before:
node dist/server.js
# After:
bun dist/server.jsDoporučuji to pouze pro služby s vynikajícím pokrytím testy. Spusťte své zátěžové testy pod Bun, než přepnete produkci.
Proměnné prostředí a konfigurace#
Bun zpracovává .env soubory automaticky — žádný balíček dotenv není potřeba:
# .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 automaticky načítá .env, .env.local, .env.production atd., podle stejné konvence jako Next.js. Jedna závislost méně ve vašem package.json.
Zpracování chyb a ladění#
Výstup chyb Bunu se výrazně zlepšil, ale stále není tak vyladěný jako Node.js v některých případech:
# 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.tsPro VS Code přidejte toto do svého .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "bun",
"request": "launch",
"name": "Debug Bun",
"program": "${workspaceFolder}/src/server.ts",
"cwd": "${workspaceFolder}",
"stopOnEntry": false,
"watchMode": false
}
]
}Stack traces v Bun jsou obecně přesné a obsahují source mapy pro TypeScript. Hlavní mezera v ladění je, že některé Node.js-specifické ladící nástroje (jako ndb nebo clinic.js) s Bun nefungují.
Bezpečnostní aspekty#
Pár věcí k zamyšlení, pokud hodnotíte Bun pro produkci:
Vyspělost: Node.js je v produkci už 15+ let. Každý okrajový případ v parsování HTTP, zpracování TLS a proudovém zpracování byl nalezen a opraven. Bun je mladší. Je dobře otestovaný, ale prostor pro neobjevené chyby je větší.
Bezpečnostní záplaty: Tým Bunu dodává aktualizace často, ale bezpečnostní tým Node.js má formální proces CVE, koordinované zveřejňování a delší track record. Pro bezpečnostně kritické aplikace to hraje roli.
Dodavatelský řetězec: Vestavěné funkce Bunu (SQLite, HTTP server, WebSockety) znamenají méně npm závislostí. Méně závislostí znamená menší prostor pro útoky na dodavatelský řetězec. To je skutečná bezpečnostní výhoda.
# 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)To je významné snížení počtu balíčků, kterým svěřujete svou produkční zátěž.
Ladění výkonu#
Pár Bun-specifických tipů na výkon:
// 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 limitČasté záludnosti#
Po roce používání Bunu jsou zde věci, které mě zaskočily:
1. Chování globálního fetch se liší#
// 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. Chování při ukončení procesu#
// 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. Konfigurace TypeScriptu#
// 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 ve vývoji#
# 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. Konfigurační soubor 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"Můj verdikt#
Po roce produkčního používání jsem se usadil takto:
Kde Bun používám dnes#
Package manager pro všechny projekty — včetně tohoto Next.js blogu. bun install je rychlejší a kompatibilita je v podstatě perfektní. Nevidím důvod nadále používat npm nebo yarn. pnpm je jediná alternativa, kterou bych zvažoval (pro jeho přísné řešení závislostí v monorepech).
Runtime pro skripty a CLI nástroje — Jakýkoli TypeScript soubor, který potřebuji spustit jednorázově, spouštím s bun. Žádný kompilační krok. Rychlý start. Vestavěné načítání .env. V mém workflow kompletně nahradil ts-node a tsx.
Runtime pro malá API a interní nástroje — Bun.serve() + bun:sqlite je neuvěřitelně produktivní stack pro interní nástroje, webhook handlery a malé služby. Model nasazení „jeden binární soubor, žádné závislosti" je přesvědčivý.
Test runner pro jednoduché projekty — Pro projekty s přímočarými testovacími potřebami je bun test rychlý a nevyžaduje žádnou konfiguraci.
Kde zůstávám u Node.js#
Produkční Next.js — Ne proto, že Bun nefunguje, ale proto, že poměr rizika a přínosu to zatím neospravedlňuje. Next.js je komplexní framework s mnoha integračními body. Chci pod ním ten nejprověřenější runtime.
Kritické produkční služby — Mé hlavní API servery běží na Node.js za PM2. Ekosystém monitoringu, ladicí nástroje, provozní znalosti — to vše je Node.js. Bun se tam dostane, ale zatím tam není.
Cokoli s nativními addony — Pokud řetězec závislostí obsahuje C++ nativní addony, Bun ani nezkoušímmm. Nestojí za ladění problémů s kompatibilitou.
Týmy, které Bun neznají — Zavedení Bunu jako runtime týmu, který ho nikdy nepoužíval, přidává kognitivní zátěž. Jako package manager, v pořádku. Jako runtime, počkejte, až na to bude tým připraven.
Co sleduji#
Sledování kompatibility Bunu — Když dosáhne 100 % pro Node.js API, která mě zajímají, přehodnotím to.
Podpora frameworků — Next.js, Remix a SvelteKit mají různé úrovně podpory Bunu. Až jeden z nich oficiálně podpoří Bun jako produkční runtime, bude to signál.
Enterprise adopce — Jakmile firmy se skutečnými SLA budou provozovat Bun v produkci a psát o tom, otázka vyspělosti bude zodpovězena.
Řada verzí 1.2+ — Bun se pohybuje rychle. Funkce přicházejí každý týden. Bun, který používám dnes, je výrazně lepší než ten, který jsem zkoušel před rokem.
Závěr#
Bun není stříbrná kulka. Neudělá pomalou aplikaci rychlou a špatně navržené API neudělá dobře navržené. Ale je to skutečné zlepšení vývojářského zážitku pro ekosystém JavaScriptu.
Věc, kterou na Bunu oceňuji nejvíce, není žádná jednotlivá funkce. Je to snížení složitosti toolchainu. Jeden binární soubor, který instaluje balíčky, spouští TypeScript, bundluje kód a spouští testy. Žádný tsconfig.json pro skripty. Žádný Babel. Žádná separátní konfigurace test runneru. Jen bun run your-file.ts a funguje to.
Praktická rada: začněte s bun install. Je to nulové riziko, okamžitý přínos. Pak zkuste bun run pro skripty. Pak vyhodnoťte zbytek na základě vašich konkrétních potřeb. Nemusíte jít all-in. Bun funguje perfektně jako částečná náhrada a tak by ho dnes většina lidí měla pravděpodobně používat.
Krajina JavaScriptových runtime je s Bun lepší. Konkurence dělá lepší i Node.js — Node.js 22+ se výrazně zrychlil, částečně v reakci na tlak Bunu. Vyhrávají všichni.