Przenoszenie projektu Node.js na Bun: mechanizmy działania w czasie wykonywania, Lambda i migracja
Zrozum, co zastępuje Bun w środowisku Node.js, dlaczego uruchamia się szybko, jak obsługuje TypeScript oraz w AWS Lambda, a także jak krok po kroku przeprowadzić migrację projektu.
Źródła: Everything You Need to Know About Bun: A Beginner’s Guide, Migration, and Architecture
Klasyczna usługa Node.js polega na użyciu czterech lub pięciu oddzielnych narzędzi, zanim zostanie wykonywana choćby jedna linia logiki biznesowej: menedżera pakietów, narzędzia do uruchamiania TypeScript, frameworku testowego oraz narzędzia do łączenia plików. Bun łączy te komponenty w jeden plik wykonywalny, który pełni funkcję środowiska działania, menedżera pakietów, narzędzia do łączenia plików oraz narzędzia do testowania, przy czym ma zastąpić Node.js. Ten przewodnik wyjaśnia, co dokładnie zastępuje Bun, skąd bierze się jego szybkość, jak radzi sobie z TypeScript i wdrażaniem bez serwera oraz jak przenieść istniejący projekt bez niespodzianek. Aby zapoznać się z szerszym porównaniem opartym na testach wydajnościowych, obejmującym również Deno, zobacz Node.js, Deno i Bun w porównaniu.
Czym Bun zastępuje elementy typowej ścieżki narzędziowej
Aby uruchamiać JavaScript poza przeglądarką, potrzebny jest środowisko wykonawcze, a od ponad dekady jest to Node.js. Współczesny projekt w Node zazwyczaj obejmuje jednak kilka niezależnych narzędzi:
- npm lub Yarn do instalowania i zarządzania pakietami;
tsc,ts-nodelubtsxdo kompilowania lub uruchamiania TypeScript;- Jest lub Vitest do testów jednostkowych;
- esbuild, webpack lub Rollup do tworzenia plików do publikacji.
Bun łączy wszystkie te funkcje w jednym pliku binarnym. Jego instalacja zapewnia środowisko wykonawcze, menedżer pakietów, narzędzie do łączenia plików, narzędzie do testowania oraz możliwość bezpośredniego uruchamiania TypeScript bez dodatkowej konfiguracji. Prawdziwą zaletą jest eliminacja zbędnych elementów pośrednich: mniej plików konfiguracyjnych i mniej niezgodności wersji między narzędziami.
Porównanie zalet i wad w stosunku do Node.js
Decyzja zależy przede wszystkim od szybkości i integracji z jednej strony oraz dojrzałości ekosystemu z drugiej.
Gdzie Bun ma przewagę
- Jeden zintegrowany zestaw narzędzi. Nie ma osobnego narzędzia do wykonywania testów, kompilatora plików ani transpilera TypeScript do konfiguracji.
- Szybkie instalacje. Według własnych pomiarów Bun,
bun installjest nawet 30 razy szybszy odnpm; Twoje wyniki będą zależeć od projektu i stanu pamięci podręcznej. - Szybkie uruchamianie po raz pierwszy. Procesy rozpoczynają się w ułamku sekundy, co jest szczególnie ważne w środowiskach serverless, gdzie opłacana jest latencja uruchamiania i odczuwana przez użytkowników.
- TypeScript bez konfiguracji. Pliki
.tsi.tsxdziałają bezpośrednio, bez konieczności przetwarzania lub skryptów ładowczych.
.env ładowają się automatycznie bez użycia dotenv, a środowisko wykonawcze zawiera wbudowane klienty do SQLite, Postgres, S3 i Redis.Gdzie Node.js nadal wygrywa
- Zdojrzałość. Node.js ma ponad 15 lat historii stosowania w produkcji oraz oficjalne wersje długoterminowej obsługi, na których planują przedsiębiorstwa. Bun jest młodszy i nadal eliminuje braki w kompatybilności w szczególnych przypadkach.
- Natywne dodatki. Zdecydowana większość pakietów npm (często podawane jako ponad 95%) działa bez zmian, ale starsze pakety, które kompilują kod C++ za pomocą
node-gyp, mogą nadal napotykać problemy.
Dlaczego Bun uruchamia się i działa szybko
To nie jest po prostu „Zig przewyższa C++”
Bun jest napisany w Zig, więc łatwo jest przypisać jego szybkość językowi. W praktyce skompilowany Zig i skompilowany C++ działają mniej więcej z taką samą szybkością na procesorze. Różnica wynika z projektowania. Node.js posiada wiele lat nagromadzonych abstrakcji, podczas gdy Bun ponownie zaimplementował operacje wejścia/wyjścia plików, obsługę ciągów znaków oraz sieci od zera, kładąc duży nacisk na unikanie niepotrzebnych alokacji pamięci.
Inny silnik JavaScript
Node.js działa na V8, silniku stworzonym przez Google dla Chrome. Bun wykorzystuje JavaScriptCore, silnik opracowany przez Apple dla Safari. JavaScriptCore jest zoptymalizowany pod kątem szybkiego uruchamiania i intensywnej optymalizacji pamięci, co pomaga wyjaśnić lepsze wyniki startu aplikacji w Bun. Każdy silnik ma zadania, w których jest szybszy, dlatego usługi o długim czasie działania i dużym obciążeniu obliczeniowym powinny być sprawdzane pod kątem wydajności, zamiast zakładać automatyczne poprawy.
Korzyści nie ograniczają się tylko do macOS
Mimo że JavaScriptCore powstał w Safari, zarówno on, jak i Bun są kompilowane natywnie dla Linuxa na architekturach x86_64 i ARM64 oraz dla Windows. Charakterystyki wydajności przenoszą się na zwykłe serwery chmurowe, niezależnie od systemu operacyjnego.
Uruchamianie Bun na AWS Lambda
Bun może obsługiwać serwery backendowe HTTP za pomocą swojego wbudowanego interfejsu API Bun.serve() lub poprzez znane frameworki takie jak Express, Fastify i Hono. W środowisku AWS Lambda jego szybkie uruchamianie po raz pierwszy może zmniejszyć zarówno opóźnienia, jak i koszty wykonywania. Lambda nie oferuje zarządzanego środowiska wykonawczego Bun do wyboru w konsoli, dlatego zespoły zazwyczaj wybierają jedną z trzech metod:
- Swoja warstwa środowiska wykonawczego. Publikuj binarz
bunjako warstwę Lambda i uruchamiaj go na podstawowym obrazie Linuxaprovided.al2023. - Obraz kontenera. Twórz funkcję na podstawie oficjalnego obrazu Dockera
oven/bun, umieszczaj go w Amazon ECR i wdrażaj jako kontener. - Buduj z użyciem Bun, uruchamiaj na Node. Użyj
bun build, aby spakować kod do jednego pliku.js, a następnie uruchom ten plik w zarządzanym środowisku Node.js dostępnym w Lambda.
Trzecia opcja jest najbardziej konserwatywna: uzyskujesz szybkie pakowanie oferowane przez Bun, zachowując jednocześnie obsługiwany środowisko wykonawcze AWS w produkcji. Rezygnujesz z API specyficznych dla środowiska Bun oraz z przewagi szybkiego uruchamiania, więc nadaje się ona zespołom, które chcą wprowadzać Bun stopniowo.
Jak Bun wykonuje TypeScript
Możesz uruchamiać pliki bezpośrednio za pomocą polecenia bun, niezależnie od tego, czy są to pliki JavaScript, czy TypeScript: bun index.js, bun index.ts lub bun --watch index.ts, aby automatycznie restartować aplikację po zmianie plików.
Gdy uruchamiasz plik .ts, kompilator Bun usuwa adnotacje typów w czasie rzeczywistym i robi to wyłącznie w pamięci. Nie zapisuje on folderu dist/, katalogu .build/ ani tymczasowych plików .js na dysku.
Kluczowym zastrzeżeniem jest to, że usuwanie informacji o typach nie jest tym samym co ich sprawdzanie. Aby uruchomienie było natychmiastowe, Bun nie przeprowadza w czasie wykonywania żadnej statycznej weryfikacji typów, więc kod z błędami typów nadal będzie działał. Sprawdzanie typów odbywa się w edytorze, takim jak VS Code lub Cursor, oraz w środowisku CI, gdzie można uruchomić bunx tsc --noEmit. Traktuj ten krok w środowisku CI jako obowiązkowy.
Migracja istniejącego projektu Node.js krok po kroku
Ponieważ Bun jest zgodny z API Node’a i konwencjami npm, zwykły projekt można przenieść przy użyciu zaledwie kilku zmian. Wykonaj to na gałęzi i na końcu uruchom cały zestaw testów.
Krok 1: Usuń stare moduły i pliki lockfile
Zacznij od czystego drzewa zależności, aby Bun mógł sam rozwiązać wszystkie problemy: usuń folder node_modules wraz z plikiem blokującym używanym przez projekt, niezależnie od tego, czy jest to package-lock.json, yarn.lock czy pnpm-lock.yaml.
Usunięcie starego pliku blokującego oznacza, że wersje pakietów są ponownie wybierane na podstawie zakresów określonych w package.json, więc możesz otrzymać nowsze wydania niż wcześniej. Jeśli ważna jest dokładna powtarzalność, sprawdź, czy twoja wersja Bun może zamiast tego migrować istniejący plik blokujący, oraz przejrzyj różnice pomiędzy rozwiązanymi wersjami.
Krok 2: Zainstaluj za pomocą Bun
Zrób bun install. Bun odczytuje twój istniejący plik package.json, pobiera pakiety i tworzy plik bun.lock, który powinieneś zgłosić do repozytorium.
Krok 3: Zaktualizuj skrypty w package.json
Zamień odniesienia do node, ts-node lub tsx w sekcji scripts:
- start:
"bun src/index.js"zastępuje"node src/index.js"; - dev:
"bun --watch src/index.ts"zastępuje"ts-node-dev src/index.ts"; - test:
"bun test"zastępuje"jest"lub"vitest".
Uruchamiający testy jest w dużej mierze zgodny z API Jest, ale testy, które silnie polegają na specyficznych dla Jest mechanizmach symulacji lub konfiguracji, mogą wymagać dostosowań.
Krok 4: Zmień definicje TypeScript
Dla projektów typu TypeScript zastąp definicje typów Node tymi dostarczanymi przez Bun, uruchamiając bun remove @types/node, a następnie bun add -d @types/bun.
Następnie zaktualizuj wpis types w polu compilerOptions pliku tsconfig.json. Starsze przewodniki odnoszą się tam do "bun-types"; przy obecnym pakiecie @types/bun wpis ten to zazwyczaj "bun", więc sprawdź dokumentację Bun TypeScript dla wersji, którą instalujesz.
Krok 5: Zastąpienie nieodpowiednich modułów
Standardowe moduły Node, takie jak node:fs, node:path i node:http, działają bez zmian. Problemy zazwyczaj powstają z pakietami, które polegają na natywnych bindingach C++, a dla kilku z nich istnieją proste zamienniki:
- Zastąp
bcryptprzezbcryptjslub@node-rs/bcrypt; - Zastąp
sqlite3wbudowanym w Bun modułembun:sqlite;
dotenv, ponieważ Bun automatycznie ładowa plik .env.Należy starannie przemyśleć tę ostatnią zmianę. Jeśli ten sam kod musi nadal działać w środowisku Node, na przykład w powyższej konfiguracji Lambda przeznaczonej wyłącznie do budowania, zachowaj dotenv lub ładowaj konfigurację wyraźnie, aby zachowanie nie różniło się między poszczególnymi środowiskami.
Główne wnioski
- Główną zaletą Bun jest konsolidacja: jeden plik binarny zastępuje menedżera pakietów, narzędzie do obsługi TypeScript, framework testowy oraz narzędzie do pakowania.
- Jego szybkość wynika z optymalnej reimplementacji oraz szybkiego uruchamiania JavaScriptCore, a nie z faktu, że Zig jest z natury szybszy od C++.
- Bun usuwa typy TypeScript z pamięci, ale nigdy ich nie sprawdza, dlatego w procesie CI należy zachować opcję
tsc --noEmit. - W Lambda można wybrać między własną warstwą, obrazem kontenera lub pakowaniem za pomocą Bun i uruchamianiem na zarządzanym Node.js.
- Migracja odbywa się głównie w sposób mechaniczny, ale należy zwrócić uwagę na ponowne rozwiązanie problemu plików lockfile, dodatki natywne oraz wszelkie zachowania zależne od automatycznego ładowania plików
.env.