Ein Node.js-Projekt auf Bun übertragen: Laufzeitinternas, Lambda und Migration
Verstehen Sie, was Bun in einer Node.js-Toolkette ersetzt, warum es schnell startet, wie es TypeScript sowie auf AWS Lambda ausführt, und wie man ein Projekt schrittweise migriert.
Quellen: Everything You Need to Know About Bun: A Beginner’s Guide, Migration, and Architecture
Ein typischer Node.js-Dienst benötigt vier oder fünf separate Tools, bevor er eine einzige Zeile an Geschäftslogik ausführen kann: einen Paketmanager, einen TypeScript-Compiler, ein Testframework und einen Bundler. Bun fasst all diese Komponenten zu einem einzigen ausführbaren Programm zusammen, das als Laufzeitumgebung, Paketmanager, Bundler und Teststarter fungiert, und soll als Ersatz für Node.js dienen. Diese Anleitung erläutert, was Bun tatsächlich ersetzt, woher seine Geschwindigkeit stammt, wie er mit TypeScript sowie serverlosen Deployment-Methoden umgeht und wie man ein bestehendes Projekt ohne Überraschungen darauf umstellt. Für einen umfassenderen, auf Benchmarks basierenden Vergleich, der auch Deno umfasst, siehe Node.js, Deno und Bun im Vergleich.
Was Bun in einer typischen Toolkette ersetzt
Um JavaScript außerhalb des Browsers auszuführen, ist ein Laufzeitumfeld erforderlich – seit über einem Jahrzehnt bedeutet das Node.js. Ein modernes Node-Projekt nutzt jedoch in der Regel mehrere unabhängige Tools:
- npm oder Yarn zur Installation und Verwaltung von Paketen;
tsc,ts-nodeodertsxzur Kompilierung oder Ausführung von TypeScript;- Jest oder Vitest für Unit-Tests;
- esbuild, webpack oder Rollup zur Erstellung von Produktionsbündeln.
Bun bietet all diese Funktionen in einem einzigen Binärdatei an. Durch die Installation erhält man ein Laufzeitumfeld, einen Paketmanager, einen Bündler, einen Testausführer sowie die direkte Ausführung von TypeScript – ohne zusätzliche Konfiguration. Der eigentliche Vorteil besteht darin, dass überflüssige Verbindungen entfernt werden: weniger Konfigurationsdateien und weniger Versionskonflikte zwischen den Tools.
Vergleich der Vor- und Nachteile gegenüber Node.js
Die Entscheidung hängt hauptsächlich von Geschwindigkeit und Integration auf der einen Seite sowie von der Reifegrad des Ökosystems auf der anderen Seite ab.
Wo Bun stark ist
- Eine integrierte Toolkette. Es gibt keinen separaten Testausführer, Bundler oder TypeScript-Transpiler, den man konfigurieren muss.
- Schnelle Installationen. Laut eigenen Messungen von Bun ist
bun installbis zu 30 Mal schneller alsnpm; Ihre Ergebnisse hängen vom Projekt und vom Cache-Zustand ab. - Schnelle Kaltstartzeiten. Prozesse starten in Millisekunden, was besonders in serverlosen Umgebungen wichtig ist, wo die Kaltstartverzögerung berechnet und vom Benutzer gespürt wird.
- TypeScript ohne Einrichtung.
.ts- und.tsx-Dateien laufen direkt ab, ohne Build-Schritte oder Loader-Skripte.
.env-Dateien werden ohne dotenv automatisch geladen, und die Laufzeitumgebung enthält eingebaute Clients für SQLite, Postgres, S3 und Redis.Wo Node.js immer noch vorteilhaft ist
- Reife. Node.js verfügt über eine Produktionsgeschichte von mehr als 15 Jahren sowie formelle Langzeitunterstützungsversionen, auf die Unternehmen planen können. Bun ist jünger und schließt noch Kompatibilitätslücken in Randfällen.
- Eigene Erweiterungen. Die überwiegende Mehrheit der npm-Pakete (oft mit über 95% angegeben) funktioniert ohne Änderungen, doch ältere Pakete, die C++-Code über
node-gypkompilieren, können immer noch Probleme verursachen.
Warum Bun schnell startet und läuft
Es ist nicht einfach „Zig schlägt C++“
Bun ist in Zig geschrieben, und es ist verlockend, seine Geschwindigkeit der Sprache zuzuschreiben. In der Praxis laufen kompilierte Zig- und C++-Programme auf dem CPU in etwa mit derselben Geschwindigkeit. Der Unterschied liegt im Design. Node.js verfügt über viele Jahre an angesammelten Abstraktionen, während Bun Datei-Eingabe/Ausgabe, Zeichensatzverarbeitung und Netzwerkfunktionen von Grund auf neu implementiert hat, wobei besonderer Wert darauf gelegt wird, unnötige Speicherzuweisungen zu vermeiden.
Ein anderer JavaScript-Engine
Node.js läuft auf V8, dem Motor, den Google für Chrome entwickelt hat. Bun verwendet JavaScriptCore, den Motor, den Apple für Safari entwickelt hat. JavaScriptCore ist auf schnellen Start und aggressive Speicheroptimierung abgestimmt, was dazu beiträgt, die Leistungsdaten von Bun bei erster Ausführung zu erklären. Jeder Motor weist Aufgabenbereiche auf, in denen er schneller ist; daher sollten langlaufende, rechenintensive Dienste geprüft werden, anstatt davon auszugehen, dass sie automatisch verbessert werden.
Die Vorteile beschränken sich nicht auf macOS
Auch wenn JavaScriptCore ursprünglich aus Safari stammt, werden sowohl dieser als auch Bun nativ für Linux auf x86_64 und ARM64 sowie für Windows kompiliert. Die Leistungsmerkmale gelten auch für gewöhnliche Cloud-Server unabhängig vom Betriebssystem.
Bun auf AWS Lambda ausführen
Bun kann HTTP-Backends über seine eigene Bun.serve()-API oder über bekannte Frameworks wie Express, Fastify und Hono bereitstellen. Auf AWS Lambda können seine schnellen Kaltstartzeiten sowohl die Latenz als auch die Ausführungskosten verringern. Lambda bietet keinen verwalteten Bun-Runtime an, den man im Konsole auswählen könnte, weshalb Teams in der Regel einen von drei Ansätzen wählen:
- Kundenspezifische Runtime-Schicht. Veröffentlichen Sie das
bun-Binärdatei als Lambda-Schicht und führen Sie es auf dem Basis-Linux-Bildprovided.al2023aus. - Container-Bild. Erstellen Sie die Funktion aus dem offiziellen
oven/bun-Docker-Bild, pushen Sie es in Amazon ECR und deployen Sie es als Container. - Bauen mit Bun, ausführen auf Node. Verwenden Sie
bun build, um den Code in eine einzige.js-Datei zu packen, und führen Sie diese Datei anschließend auf Lambdas verwaltetem Node.js-Runtime aus.
Die dritte Option ist die konservativste: Sie erhalten Buns schnelles Bundling, während Sie den von AWS unterstützten Laufzeitumgebung in der Produktion beibehalten. Sie verzichten auf Buns laufzeitumspezifische APIs sowie den Vorteil des schnellen Starts, wodurch sie sich für Teams eignet, die Bun schrittweise einführen möchten.
Wie Bun TypeScript ausführt
Sie führen Dateien direkt mit dem Befehl bun aus, unabhängig davon, ob es sich um JavaScript oder TypeScript handelt: bun index.js, bun index.ts oder bun --watch index.ts, um bei Dateiänderungen automatisch neu zu starten.
Wenn Sie eine .ts-Datei ausführen, entfernt Buns Transpiler die Typangaben direkt und vollständig im Speicher. Es wird weder eine dist/-Ordnerstruktur, ein .build/-Verzeichnis noch temporäre .js-Dateien auf die Festplatte geschrieben.
Die wichtige Einschränkung ist, dass das Entfernen von Typen nicht dasselbe ist wie ihre Überprüfung. Um einen sofortigen Start zu gewährleisten, führt Bun keine statische Typüberprüfung zur Laufzeit durch, sodass Code mit Typfehlern dennoch ausgeführt werden kann. Die Typüberprüfung findet weiterhin in Ihrem Editor, wie z. B. VS Code oder Cursor, sowie im CI statt, wo Sie bunx tsc --noEmit ausführen können. Betrachten Sie diesen CI-Schritt als verpflichtend.
Schrittweise Migration eines bestehenden Node.js-Projekts
Weil Bun den APIs von Node sowie den Konventionen von npm folgt, lässt sich ein herkömmliches Projekt in der Regel mit nur wenigen Änderungen migrieren. Führen Sie die Migration auf einer Branch durch und prüfen Sie am Ende Ihren gesamten Testumfang.
Schritt 1: Alte Module und Lockfiles entfernen
Starten Sie mit einem sauberen Abhängigkeitsbaum, damit Bun alles selbst auflöst: Entfernen Sie den Ordner node_modules zusammen mit dem jeweiligen Lockfile, das das Projekt verwendet hat – egal ob es sich um package-lock.json, yarn.lock oder pnpm-lock.yaml handelt.
Durch das Löschen des alten Lockfiles werden die Versionen erneut aus den Angaben in package.json bestimmt, sodass Sie möglicherweise neuere Versionen erhalten als zuvor. Wenn eine exakte Wiederholbarkeit wichtig ist, prüfen Sie, ob Ihre Bun-Version stattdessen ein vorhandenes Lockfile migrieren kann, und überprüfen Sie den Unterschied der auflösenden Versionen.
Schritt 2: Mit Bun installieren
Führen Sie bun install aus. Bun liest Ihr vorhandenes package.json, lädt die Pakete herunter und erstellt eine bun.lock-Datei, die Sie commitpen sollten.
Schritt 3: Die Skripte in package.json aktualisieren
Ersetzen Sie die Verweise auf node, ts-node oder tsx im Abschnitt scripts:
- start:
"bun src/index.js"ersetzt"node src/index.js"; - dev:
"bun --watch src/index.ts"ersetzt"ts-node-dev src/index.ts"; - test:
"bun test"ersetzt"jest"oder"vitest".
Der Testausführer ist größtenteils mit Jest’s API kompatibel, doch Tests, die stark auf jest-spezifischem Mocking oder Konfigurationen beruhen, könnten Anpassungen erfordern.
Schritt 4: Wechseln Sie zu den TypeScript-Definitionen
Für TypeScript-Projekte ersetzen Sie die Node-Typdefinitionen durch Bun’s, indem Sie zuerst bun remove @types/node ausführen und anschließend bun add -d @types/bun.
Dann aktualisieren Sie den Eintrag types in compilerOptions von tsconfig.json. Ältere Anleitungen beziehen sich dort auf "bun-types"; mit dem aktuellen @types/bun-Paket lautet der Eintrag in der Regel "bun", daher sollten Sie die Bun TypeScript-Dokumentation für die von Ihnen installierte Version prüfen.
Schritt 5: Ersatz für nicht kompatible Module
Standardmäßige Node-Module wie node:fs, node:path und node:http funktionieren unverändert. Pakete, die auf nativen C++-Bindings angewiesen sind, sind in der Regel die Quelle von Problemen, und für einige gibt es einfache Ersatzlösungen:
- Ersetzen Sie
bcryptdurchbcryptjsoder@node-rs/bcrypt; - Ersetzen Sie
sqlite3durch Bun’s eingebautesbun:sqlite;
dotenv-Aufrufe entfernen, da Bun die .env-Datei automatisch lädt.Sie sollten bei dieser letzten Änderung sorgfältig vorgehen. Wenn derselbe Code beispielsweise auch unter Node ausgeführt werden muss, wie in der oben beschriebenen reinen Build-Lambda-Einrichtung, sollten Sie dotenv beibehalten oder die Konfiguration explizit laden, damit sich das Verhalten zwischen den Laufzeiten nicht unterscheidet.
Haupterkenntnisse
- Der größte Vorteil von Bun liegt in der Konsolidierung: Ein einziges Binärdatei ersetzt den Paketmanager, den TypeScript-Compiler, das Testframework und den Bundler.
- Ihre Geschwindigkeit resultiert aus einer ressourcenschonenden Neureализierung sowie dem schnellen Start von JavaScriptCore – nicht davon, dass Zig im Grunde schneller als C++ ist.
- Bun entfernt die TypeScript-Typen aus dem Speicher, prüft sie jedoch nie; daher sollten Sie in CI weiterhin
tsc --noEmitverwenden. - Auf Lambda können Sie zwischen einer benutzerdefinierten Schicht, einem Containerbild oder dem Bundeln mit Bun unter Verwendung eines verwalteten Node.js-Systems wählen.
- Die Migration ist größtenteils mechanisch, achten Sie jedoch auf die erneute Verarbeitung von Lockfiles, native Erweiterungen sowie jegliches Verhalten, das von der automatischen Ladeung von
.env-Dateien abhängt.