Bun, jeden plik binarny zamiast czterech narzędzi
Bun zastępuje cztery rzeczy naraz: uruchamia kod jak Node, instaluje zależności jak npm, buduje paczkę jak esbuild i wykonuje testy jak Jest. Wszystko siedzi w jednym pliku binarnym napisanym w Zigu i opartym o silnik JavaScriptCore, ten sam, który napędza Safari.
Co to zmienia w codziennej pracy
Typowy projekt w JavaScripcie ma dziś cztery narzędzia od czterech zespołów, każde z własną konfiguracją i własnym cyklem wydawniczym. Bun scala je w jedno, więc znika warstwa konfiguracji spinającej je ze sobą.
Najbardziej odczuwalna zmiana dotyczy TypeScriptu. Bun wykonuje pliki .ts i .tsx bezpośrednio, bez etapu kompilacji i bez dodatkowego narzędzia w środku.
bun run skrypt.tsTen sam plik w Node wymaga albo wcześniejszej kompilacji, albo narzędzia przechwytującego import. W praktyce oznacza to szybszy start i mniej rzeczy, które potrafią się zepsuć przy zmianie wersji.
Druga zmiana to prędkość instalacji zależności. Różnica wobec tradycyjnych menedżerów bywa kilkukrotna, co widać najbardziej w środowisku ciągłej integracji, gdzie instalacja powtarza się przy każdym przebiegu.
Menedżer pakietów zgodny z ekosystemem
Bun czyta package.json i instaluje z rejestru npm, więc nie wymaga zmiany sposobu opisywania zależności.
bun install
bun add zod
bun add -d vitest
bun remove lodashWarto wiedzieć o jednej rzeczy przy pracy zespołowej. Bun tworzy własny plik blokady, a jego domyślny format bywał binarny, przez co diff w pull requeście nic nie mówi. Jeśli reszta zespołu pracuje na innym menedżerze, ustalcie jeden, bo dwa równoległe pliki blokady prowadzą do sytuacji, w której każdy ma inną wersję biblioteki.
Instalacja globalna narzędzi też działa, a bunx uruchamia pakiet bez instalowania go na stałe, analogicznie do polecenia znanego z npm.
Wbudowany test runner
Testy uruchamiasz bez dokładania biblioteki, a składnia jest zgodna z tym, co zna większość zespołów.
import { test, expect, describe } from "bun:test"
describe("kalkulator rabatu", () => {
test("nalicza rabat progowy", () => {
expect(policzRabat(1200)).toBe(120)
})
test("nie nalicza ponizej progu", () => {
expect(policzRabat(300)).toBe(0)
})
})bun test
bun test --watch
bun test --coveragePrzy migracji z Jesta większość testów jednostkowych przechodzi bez zmian. Kłopoty zaczynają się przy testach opartych o rozbudowane atrapy modułów oraz przy bibliotekach zakładających konkretne środowisko, więc przed decyzją uruchom najpierw swój obecny zestaw i sprawdź, co się wywala.
Serwer HTTP i wbudowana baza
Bun ma własny serwer oparty o standardowe obiekty Request i Response, te same, które znasz z przeglądarki i z tras API w Next.js.
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url)
if (url.pathname === "/zdrowie") return new Response("ok")
return Response.json({ blad: "nie znaleziono" }, { status: 404 })
}
})Do tego dochodzi klient SQLite wbudowany w runtime, bez instalowania modułu natywnego.
import { Database } from "bun:sqlite"
const db = new Database("dane.sqlite")
db.run("CREATE TABLE IF NOT EXISTS zdarzenia (id INTEGER PRIMARY KEY, nazwa TEXT)")
db.query("INSERT INTO zdarzenia (nazwa) VALUES (?)").run("start")Baza w pliku ma jedno ograniczenie warte nazwania od razu: leży na dysku tej maszyny, więc druga instancja usługi widzi własną kopię, a wdrożenie na kilku maszynach przestaje mieć sens. Gdy to zaczyna przeszkadzać, Turso daje tę samą składnię SQLite jako usługę, z repliką synchronizowaną do wnętrza aplikacji, kosztem odczytów pokazujących stan sprzed chwili.
Poza SQLite runtime ma też wbudowane klienty do popularnych baz i pamięci podręcznej, więc typowa usługa obywa się bez kilku zależności, które zwykle instaluje się odruchowo. Mniej zależności to mniej aktualizacji bezpieczeństwa do śledzenia, co przy małych projektach bywa ważniejsze niż sama prędkość.
To układ, który dobrze pasuje do małych usług i narzędzi wewnętrznych: jeden plik binarny, jedna baza w pliku, zero zależności do zainstalowania na serwerze.
Bundler i budowanie paczki
Czwartym narzędziem w tym zestawie jest bundler. Buduje z niego zarówno kod przeglądarkowy, jak i pojedynczy plik serwerowy do wdrożenia.
bun build ./src/index.ts --outdir ./dist --target node --minify
bun build ./src/app.tsx --outdir ./public --target browser --splittingParametr target decyduje o tym, jak potraktowane zostaną wbudowane moduły. Wersja przeglądarkowa wyrzuca odwołania do systemu plików, wersja serwerowa je zachowuje, a wariant dla samego Buna korzysta z jego własnych interfejsów.
W praktyce bundler nadaje się dobrze do bibliotek, narzędzi wiersza poleceń i małych aplikacji. Przy rozbudowanym frontendzie z wieloma wtyczkami ekosystem wokół dojrzalszych narzędzi bywa nadal bogatszy, więc sprawdź, czy potrzebne wtyczki mają odpowiedniki, zanim przepiszesz konfigurację budowania.
Osobno warto znać opcję budowania pojedynczego pliku wykonywalnego, który zawiera kod razem z runtime'em.
bun build ./cli.ts --compile --outfile narzedzieEfektem jest plik uruchamiany bez instalowania czegokolwiek. Dla narzędzi rozprowadzanych wewnątrz firmy rozwiązuje to problem, który zwykle kończy się instrukcją o instalacji Node w odpowiedniej wersji. Cena to rozmiar pliku liczony w dziesiątkach megabajtów, bo runtime idzie w komplecie.
Skrypty zamiast narzędzi pomocniczych
Bun dobrze sprawdza się jako zamiennik skryptów pomocniczych, które w wielu projektach są napisane w powłoce i nikt ich nie rozumie po pół roku.
// scripts/porzadki.ts
import { readdir, rm } from "node:fs/promises"
const katalogi = await readdir("./tmp")
for (const katalog of katalogi) {
if (katalog.startsWith("build-")) {
await rm(`./tmp/${katalog}`, { recursive: true })
console.log("usunieto", katalog)
}
}Uruchamiasz to poleceniem bun scripts/porzadki.ts, bez kompilacji i bez dokładania zależności. Przy typowanym kodzie łatwiej to utrzymać niż skrypt w powłoce, a różnice między systemami operacyjnymi przestają być problemem.
Wbudowane interfejsy pokrywają większość potrzeb takich skryptów: odczyt i zapis plików, wywołania sieciowe, uruchamianie podprocesów, dostęp do bazy w pliku. Przy zadaniach cyklicznych, na przykład raporcie generowanym co noc, całość mieści się w jednym pliku i jednym poleceniu w harmonogramie.
Warto tylko pamiętać o obsłudze błędów. Skrypt kończący się cicho po nieudanym wywołaniu sieciowym jest gorszy niż jego brak, bo tworzy wrażenie, że zadanie zostało wykonane. Ustaw kod wyjścia różny od zera przy niepowodzeniu, żeby harmonogram albo pipeline to zauważył.
Zgodność z Node i realne ograniczenia
Zgodność rośnie z każdym wydaniem i większość popularnych bibliotek działa bez zmian. Problemy pojawiają się w trzech miejscach i warto je sprawdzić przed decyzją o migracji.
Moduły natywne kompilowane pod Node bywają najbardziej kłopotliwe, zwłaszcza starsze sterowniki baz danych i biblioteki do przetwarzania obrazów. Rzadziej używane fragmenty standardowego API czasem zachowują się inaczej w przypadkach brzegowych. Narzędzia zakładające obecność konkretnego środowiska, na przykład część wtyczek do budowania, mogą wymagać obejścia.
Praktyczny sposób sprawdzenia zajmuje kwadrans: zainstaluj zależności Bunem i uruchom cały zestaw testów. Jeśli przechodzi, ryzyko migracji jest niewielkie. Jeśli wywala się na module natywnym, wiesz to zanim przepiszesz konfigurację wdrożeniową.
Wersje z linii 1.3 są stabilne i używane produkcyjnie, a każde kolejne wydanie domyka kolejne różnice wobec Node. Przed aktualizacją w projekcie produkcyjnym sprawdź listę zmian, bo tempo rozwoju jest tu wyraźnie wyższe niż w dojrzałych runtime'ach.
Skąd bierze się różnica w prędkości
Warto rozumieć, na czym polega przewaga, bo nie każda część projektu odczuje ją tak samo.
Pierwszym źródłem jest silnik. Bun używa JavaScriptCore zamiast V8, a ten startuje szybciej przy niewielkim narzucie pamięci. Przy krótkich procesach, jak skrypty i funkcje uruchamiane na żądanie, sam czas startu bywa zauważalną częścią całości.
Drugim jest sposób pracy menedżera pakietów. Instalacja korzysta z równoległych operacji na plikach i pamięci podręcznej trzymanej lokalnie, przez co powtórna instalacja tych samych zależności jest wielokrotnie szybsza niż pierwsza. To dlatego największą różnicę widać w środowisku ciągłej integracji, gdzie ten krok powtarza się bez końca.
Trzecim jest brak warstw pośrednich. Uruchomienie pliku TypeScript nie wymaga osobnego procesu kompilującego, a test runner nie ładuje własnego środowiska modułów.
Czego przewaga nie obejmuje: samego wykonywania kodu aplikacyjnego przy dłuższej pracy. Gdy proces działa godzinami, kompilator w silniku i tak zoptymalizuje gorące fragmenty, a różnica między silnikami spłaszcza się. Jeśli Twoja usługa jest wolna z powodu zapytań do bazy albo wywołań sieciowych, zmiana runtime'u niczego nie naprawi.
Dlatego najuczciwszy sposób oceny to pomiar na własnym projekcie: czas instalacji zależności, czas przebiegu testów i czas startu procesu. Te trzy liczby zwykle wystarczają do decyzji, a syntetyczne porównania rzadko przekładają się na codzienną pracę.
Bun kontra Node i Deno
| Narzędzie | Mocna strona | Słabość | Kiedy wybrać |
|---|---|---|---|
| Bun | Wszystko w jednym, szybka instalacja, TypeScript bez kompilacji | Młodszy ekosystem, rzadkie przypadki niezgodności | Nowy projekt, narzędzia wewnętrzne, skrypty |
| Node | Największa zgodność, wsparcie każdego hostingu | Osobne narzędzia do budowania i testów | Projekt produkcyjny z dużą liczbą zależności |
| Deno | Uprawnienia domyślnie ograniczone, wbudowane narzędzia | Inny model zależności w starszych wersjach | Skrypty wymagające kontroli dostępu |
Praktyczna rada dla zespołu: Bun najłatwiej wprowadzić od zaplecza, czyli jako menedżera pakietów i narzędzie do testów, zostawiając produkcję na Node. Zyskujesz czas w środowisku ciągłej integracji bez ryzyka na produkcji, a po kilku tygodniach wiesz, czy warto iść dalej.
Wdrożenie i hosting
Nie każdy hosting obsługuje Bun natywnie, więc sprawdź to przed wyborem. Obraz kontenera rozwiązuje sprawę wszędzie tam, gdzie masz kontrolę nad środowiskiem uruchomieniowym.
FROM oven/bun:1
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
CMD ["bun", "run", "start"]Flaga wymuszająca zgodność z plikiem blokady jest tu istotna: bez niej build potrafi zainstalować inne wersje niż te przetestowane lokalnie.
Osobną możliwością jest zbudowanie pojedynczego pliku wykonywalnego zawierającego kod i runtime. Dla narzędzi wiersza poleceń rozprowadzanych wewnątrz firmy to wygodne, bo odbiorca nie musi niczego instalować.
Przy wdrożeniach na platformy takie jak Vercel sprawdź, czy funkcja uruchamia się w środowisku wspierającym Bun, czy tylko instalacja zależności korzysta z niego w trakcie budowania. To dwie różne rzeczy i łatwo je pomylić.
Jak wprowadzić Buna w istniejącym zespole
Zmiana narzędzia w projekcie, nad którym pracuje kilka osób, rzadko rozbija się o technikę. Rozbija się o to, że połowa zespołu ma inaczej skonfigurowane środowisko i nikt nie chce spędzić dnia na diagnozowaniu cudzego błędu.
Kolejność, która działa, wygląda tak. Najpierw jedna osoba uruchamia zestaw testów na Bunie i zapisuje, co się wywala. Jeśli nic, przechodzicie do kroku drugiego, czyli podmiany menedżera pakietów w środowisku ciągłej integracji, ale jeszcze nie na maszynach deweloperskich. Ten krok jest odwracalny jedną linią w konfiguracji i od razu pokazuje zysk czasu.
Krok trzeci to lokalne środowiska, wraz z usunięciem starego pliku blokady w tym samym commicie. Dwa pliki blokady w repozytorium to najczęstsza przyczyna kłopotów przy takiej migracji, więc przejście musi być jednorazowe, a nie stopniowe.
Runtime zmieniaj na końcu i osobno, najlepiej zaczynając od usługi o najmniejszym ryzyku. Aplikacja wewnętrzna albo zadanie w tle nadaje się do tego lepiej niż główny serwis obsługujący klientów.
Zapisz też w dokumentacji projektu, jakiej wersji używacie i jak ją zainstalować. Narzędzie rozwija się szybko, a różnica kilku wydań potrafi zmienić zachowanie, którego nikt nie testował.
Typowe błędy
Pierwszy to migracja całego projektu naraz, łącznie z produkcją. Rozsądniej zacząć od instalacji zależności i testów, a runtime zmieniać osobno.
Drugi to dwa pliki blokady w repozytorium. Jeśli część zespołu używa npm, a część Buna, wersje zależności rozjeżdżają się cicho aż do pierwszego błędu tylko na jednej maszynie.
Trzeci to pominięcie flagi wymuszającej zgodność z plikiem blokady w pipelinie. Build powinien instalować dokładnie to, co przetestowano, a nie najnowsze pasujące wersje.
Czwarty to zakładanie pełnej zgodności bez sprawdzenia. Zestaw testów uruchomiony na Bunie odpowiada na to pytanie w kilkanaście minut.
Piąty to używanie wbudowanego SQLite jako bazy produkcyjnej dla aplikacji z wieloma instancjami. Plik na dysku nie dzieli się między procesami tak, jak spodziewa się tego aplikacja skalowana poziomo.
FAQ
Czy Bun zastąpi Node.js?
W nowych projektach i narzędziach wewnętrznych bywa wygodniejszy, natomiast Node pozostaje bezpieczniejszym wyborem tam, gdzie liczy się zgodność z całym ekosystemem i wsparcie każdego hostingu. Wiele zespołów używa obu: Buna do instalacji i testów, Node na produkcji.
Czy Bun działa z Next.js i Reactem?
Instalacja zależności i uruchamianie skryptów działa bez przeszkód. Przy uruchamianiu samego serwera aplikacji sprawdź zgodność w swojej wersji frameworka, bo niektóre elementy zakładają środowisko Node.
Czy trzeba zmieniać package.json?
Nie, Bun czyta ten sam plik i instaluje z rejestru npm. Zmienia się plik blokady, więc ustal z zespołem, którego menedżera używacie, żeby nie utrzymywać dwóch naraz.
Czy testy z Jesta przejdą bez zmian?
Większość testów jednostkowych tak, bo składnia jest zgodna. Poprawek wymagają zwykle rozbudowane atrapy modułów i testy zależne od konkretnego środowiska. Najszybciej sprawdzisz to, uruchamiając obecny zestaw poleceniem bun test.
Czy Bun nadaje się na produkcję?
Tak, linia 1.3 jest używana produkcyjnie, przy czym rozsądnie jest przypiąć konkretną wersję i śledzić listę zmian. Tempo wydań jest wyższe niż w dojrzałych runtime'ach, więc aktualizacja bez sprawdzenia bywa źródłem niespodzianek.
Dokumentacja stoi na bun.com/docs, a kod źródłowy w repozytorium na GitHubie.