Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds
Powrót do kolekcji
Przewodnik11 min czytania

Bun, runtime, bundler i menedzer pakietow

Bun laczy runtime, menedzera pakietow, bundler i test runner w jednym binarnym pliku. Wydajnosc, zgodnosc z Node, wbudowane SQLite i migracja projektu.

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.

Code
Bash
bun run skrypt.ts

Ten 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.

Code
Bash
bun install
bun add zod
bun add -d vitest
bun remove lodash

Warto 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.

Code
JavaScript
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)
  })
})
Code
Bash
bun test
bun test --watch
bun test --coverage

Przy 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.

Code
JavaScript
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.

Code
JavaScript
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.

Code
Bash
bun build ./src/index.ts --outdir ./dist --target node --minify
bun build ./src/app.tsx --outdir ./public --target browser --splitting

Parametr 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.

Code
Bash
bun build ./cli.ts --compile --outfile narzedzie

Efektem 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.

TSscripts/porzadki.ts
TypeScript
// 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ędzieMocna stronaSłabośćKiedy wybrać
BunWszystko w jednym, szybka instalacja, TypeScript bez kompilacjiMłodszy ekosystem, rzadkie przypadki niezgodnościNowy projekt, narzędzia wewnętrzne, skrypty
NodeNajwiększa zgodność, wsparcie każdego hostinguOsobne narzędzia do budowania i testówProjekt produkcyjny z dużą liczbą zależności
DenoUprawnienia domyślnie ograniczone, wbudowane narzędziaInny model zależności w starszych wersjachSkrypty 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.

Code
DOCKERFILE
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.