Zig, JavaScriptCore und warum Bun so schnell ist
SerieBun: Die moderne JavaScript-Runtime
Teil 2 von 5
Bun ist mehr als eine Node.js-Alternative. Die Runtime kombiniert Zig als Systemsprache mit Apples JavaScriptCore-Engine und erreicht damit Performance-Werte, die klassische JavaScript-Runtimes nicht bieten können. Ein Blick auf die Architektur zeigt, warum.
Zig und JavaScriptCore
Während Node.js auf V8 (Chrome) setzt, verwendet Bun JavaScriptCore aus Safari. Der entscheidende Unterschied liegt aber tiefer: Buns Event Loop, Task Scheduler und native I/O-Operationen sind direkt in Zig implementiert.
Was das bringt:
- Bis zu 4x schnellere Startzeiten
- Niedrigere Latenz bei I/O-Operationen
- Optimierte Module-Resolution und Caching
Zig ermöglicht direkten Speicherzugriff ohne Garbage-Collection-Overhead. Das macht sich besonders bei häufigen, kleinen Operationen bemerkbar – genau das, was Server-Anwendungen ständig tun.
Plugin-System
Buns Bundler ist über Plugins erweiterbar. Entwickler können in den Build-Prozess eingreifen und eigene Loader definieren:
import type { BunPlugin } from "bun";
const textPlugin: BunPlugin = {
name: "text-loader",
setup(build) {
build.onLoad({ filter: /\.txt$/ }, async (args) => {
const text = await Bun.file(args.path).text();
return {
contents: `export default ${JSON.stringify(text)}`,
loader: "js",
};
});
},
};
Mit build.onLoad lassen sich beliebige Dateitypen zu JavaScript-Modulen transformieren. Das ermöglicht:
- Custom Loader für proprietäre Formate
- Preprocessing von Assets
- Lazy Loading für Fullstack-Anwendungen
Tree-Shaking und Minification sind dabei automatisch integriert.
Tree-Shaking im Detail
Buns Bundler analysiert Code statisch und entfernt ungenutzte Exports durch Dead-Code-Elimination – standardmäßig aktiviert.
Bei ESM-Modulen:
// foo.js
export const foo = () => "foo";
export const bar = () => "bar";
// main.js
import { foo } from "./foo.js";
// bar wird automatisch entfernt
Der Bundler erkennt, welche Symbole tatsächlich referenziert werden und schließt nur diese ein.
Bei CommonJS:
Dynamische Exports wie module.exports = require(...) verhindern statische Analyse. In diesen Fällen bleibt das gesamte exports-Objekt erhalten. Bun konvertiert CommonJS bei Bedarf zu ESM und injiziert minimalen Wrapper-Code.
Build-Konfiguration:
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "bun",
minify: true,
env: "inline", // Tree-Shaking für Umgebungsvariablen
});
Native SQLite-Integration
Das bun:sqlite-Modul ist direkt in die Runtime integriert – kein npm-Paket, keine nativen Bindings:
import { Database } from "bun:sqlite";
const db = new Database("app.db");
// Prepared Statements
const query = db.prepare("SELECT * FROM users WHERE id = ?");
const user = query.get(1);
// Transactions
db.transaction(() => {
db.run("INSERT INTO users (name) VALUES (?)", ["Alice"]);
db.run("INSERT INTO users (name) VALUES (?)", ["Bob"]);
})();
Performance-Vorteile:
- 3-6x schneller als better-sqlite3
- Prepared Statements mit Caching
- Named und Positional Parameters
- Automatische Datatype-Conversions (BLOB → Uint8Array)
Für Low-Level-Zugriff gibt es db.fileControl mit direktem Zugang zu SQLite3-APIs. Results lassen sich auf Klassen mappen für type-safe Queries.
Native APIs
Bun bringt APIs mit, die über Standard-JavaScript hinausgehen:
Foreign Function Interface:
import { dlopen, FFIType } from "bun:ffi";
const lib = dlopen("libcrypto.so", {
MD5: {
args: [FFIType.ptr, FFIType.u64, FFIType.ptr],
returns: FFIType.ptr,
},
});
Memory-Mapped Files:
const file = Bun.file("large-data.bin");
const mapped = Bun.mmap(file);
Diese APIs ermöglichen Optimierungen, die mit Node.js nur über native Addons möglich wären.
Edge-Deployment
Für Edge-Szenarien kombiniert Bun gut mit Frameworks wie Hono:
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.json({ message: "Hello from the edge" }));
export default app;
Buns schnelle Startzeiten und niedriger Memory-Footprint machen es ideal für:
- Cloudflare Workers
- Fly.io
- Serverless-Funktionen
Die Empfehlung: Stateless Logic und minimale Dependencies priorisieren.
Wer Buns Architektur und das gesamte Ökosystem strukturiert durcharbeiten möchte, findet auf learn.casoon.dev einen vertiefenden Kurs dazu.
Quellen
- Bun Internals Explained
- The Bun Bundler
- SQLite Documentation
- Bun APIs
- Plugin Development Guide
- Edge Deployment with Bun
Was die Performance wirklich bedeutet
Bun’s Geschwindigkeitsversprechen sind real, aber differenziert:
- Startup-Zeit: Bun startet in ca. 20–40 ms, Node.js in 80–150 ms. In Lambda-Funktionen, Edge-Functions oder CLI-Tools macht das einen messbaren Unterschied.
- HTTP-Server-Performance: Native Bun-Server erreichen 200–400k req/s, Node mit Express ca. 50–80k. Diese Zahlen gelten für minimalste Endpoints — bei realer Business-Logik schrumpft der Vorteil oft auf Faktor 2–3.
- NPM-Install:
bun installist 5–25x schneller alsnpm install. Bei großen Monorepos werden aus 3 Minuten 10 Sekunden. - Test-Runner:
bun testist 2–10x schneller als Jest. Aber: Nicht alle Jest-Features sind kompatibel.
Wo JavaScriptCore Limits hat
Bun nutzt JavaScriptCore (Apples WebKit-Engine) statt V8. Das hat konkrete Implikationen:
- Manche Node.js-Module funktionieren nicht 1:1: Pakete, die V8-spezifische APIs nutzen (z. B. einige Profiling-Tools), brauchen Anpassungen oder Alternativen.
- Memory-Management ist anders: JavaScriptCore ist im Schnitt speichersparsamer als V8, aber Garbage-Collection-Pausen können bei extremer Last anders aussehen. Production-Profiling ist Pflicht.
- Debugger-Tooling: Chrome DevTools-Integration für Bun ist verfügbar, aber weniger ausgereift als für Node.js. WebStorm und VS Code-Plugins ergänzen das.
Wann Bun in Production nicht die richtige Wahl ist
- Bei strikten LTS- und Sicherheits-Audits: Node.js hat einen klar dokumentierten LTS-Zyklus (alle 12 Monate neue LTS, 30 Monate Support). Bun hat ein ähnliches Modell angekündigt, aber mit kürzerer Historie. Für regulierte Branchen oft noch nicht ausreichend.
- Bei tiefer Native-Modul-Abhängigkeit:
node-gyp-basierte Pakete funktionieren meist, aber Edge-Cases sind möglich. Bei kritischen Production-Workloads testen! - Bei Tooling-Bindung an V8-spezifische Features: Manche APM-Tools (Datadog, New Relic) haben Node.js-Plugins, die nicht vollständig Bun-kompatibel sind. Vor dem Wechsel prüfen.
Aus der Praxis: Konkrete Einsatzszenarien
- CLI-Tools: Bun gewinnt klar wegen Startup-Zeit.
- Edge-Functions/Workers: Bun ist eine gute Wahl, wenn die Plattform es unterstützt (Cloudflare bietet seit Mitte 2024 Bun-kompatible Workers).
- Hochfrequente API-Server: Bun ist deutlich schneller, aber das ist selten der Bottleneck.
- Build-Tooling/CI: Bun ist hier dramatisch schneller — ein No-Brainer für Entwicklungsumgebungen.