Головна / Статті

Перенесення проекту Node.js на Bun: внутрішні механізми роботи, Lambda та процес міграції

Зрозумійте, що замінює Bun у інструментальному комплексі Node.js, чому він запускається швидко, як він обробляє TypeScript та працює на AWS Lambda, а також як поетапно мігрувати проект.

Видавець: REACTAPP.TOP6 хв читання1493 слів

Джерела: Everything You Need to Know About Bun: A Beginner’s Guide, Migration, and Architecture

Типовий сервіс Node.js залежить від чотирьох або п’яти окремих інструментів, перш ніж виконати хоча б один рядок бізнес-логіки: менеджера пакетів, запускача TypeScript, фреймворку тестування та збирача коду. Bun об’єднує всі ці інструменти в один виконуваний файл, який виконує функції середовища виконання, менеджера пакетів, збирача коду та запускача тестів, і має на меті стати повноцінною заміною Node.js. У цьому посібнику пояснюється, що саме замінює Bun, звідки береться його швидкість, як він працює з TypeScript та серверними рішеннями без обслуговування, а також як перевести існуючий проект на нього без несподіванок. Для більш повного порівняння на основі тестів, яке також включає Deno, дивіться Node.js, Deno та Bun у порівнянні.

Що замінює Bun у типовому наборі інструментів

Для виконання JavaScript поза браузером потрібен середовище виконання, і протягом понад десяти років цим став Node.js. Однак сучасний проект на Node зазвичай включає кілька незалежних інструментів:

  • npm або Yarn для встановлення та керування пакетами;
  • tsc, ts-node або tsx для компіляції чи виконання TypeScript;
  • Jest або Vitest для одиничних тестів;
  • esbuild, webpack або Rollup для створення бандлів у продакшн-версії.

Bun постачає всі ці функції в одному бінарному файлі. Його встановлення забезпечує середовище виконання, менеджер пакетів, інструмент для створення бандлів, засіб для виконання тестів та можливість безпосереднього виконання TypeScript без додаткової налаштування. Справжньою перевагою є відсутність зайвих елементів: менше файлів конфігурації та менше неузгоджень версій між інструментами.

Порівняння переваг та недоліків порівняно з Node.js

Рішення здебільшого залежить від швидкості та інтеграції з одного боку, а від рівня зрілості екосистеми — з іншого.

Де Bun має переваги

  • Один інтегрований набір інструментів. Не потрібно налаштовувати окремий виконувач тестів, збирач або транспайлер TypeScript.
  • Швидке встановлення. За даними самого Bun, команда bun install працює вдвічі швидше за npm; ваші результати залежатимуть від проекту та стану кешу.
  • Швидке запуск без попередньої підготовки. Процеси запускаються за кілька мілісекунд, що особливо важливо у безсерверних середовищах, де затримка при запуску оплачується та відчувається користувачами.
  • TypeScript без налаштувань. Файли .ts та .tsx запускаються безпосередньо, без кроків компіляції чи скриптів-завантажувачів.
  • Нижче споживання пам’яті. Згідно з повідомленнями, HTTP-сервери використовують на 25%–40% менше оперативної пам’яті, ніж Node.js; перевірте свою власну навантаженість, перш ніж покладатися на ці дані.
  • Акумулятори входять у комплект. Файли .env завантажуються автоматично без dotenv, а середовище виконання містить вбудовані клієнти для SQLite, Postgres, S3 та Redis.
  • Де Node.js все ще має перевагу

    • Зрілість. Node.js має понад 15 років історії використання у продакшені та офіційні версії з довгостроковою підтримкою, на які розраховують корпорації. Bun є молодшим та все ще усуває проблеми з сумісністю у окремих випадках.
    • Нативні додатки. Переважна більшість пакетів npm (часто наводиться цифра понад 95%) працює без змін, але старіші пакети, які компілюють код на C++ за допомогою node-gyp, все ще можуть стикатися з проблемами.

    Чому Bun починає роботу та працює швидко

    Це не просто „Zig кращий за C++“

    Bun написаний на Zig, тож легко приписати його швидкість саме цій мові. На практиці скомпільований Zig та скомпільований C++ працюють приблизно з однаковою швидкістю на процесорі. Різниця полягає у архітектурі. Node.js містить багато років накопичених абстракцій, тоді як Bun перереалізував операції введення/виведення файлів, обробку рядків та мережевий зв’язок з нуля, роблячи акцент на уникненні зайвих виділень пам’яті.

    Інший двигун JavaScript

    Node.js працює на V8 — двигуні, створеному Google для Chrome. Bun використовує JavaScriptCore — двигун, створений Apple для Safari. JavaScriptCore налаштований під швидке запуск та інтенсивну оптимізацію пам’яті, що пояснює кращі показники швидкого запуску у Bun. Кожен двигун має сфери застосування, де він працює швидше, тому сервіси з тривалим функціонуванням та високими обчислювальними навантаженнями слід тестувати, а не просто припускати їхнє покращення.

    Переваги не обмежуються macOS

    Хоча JavaScriptCore був створений для Safari, як він, так і Bun компілюються нативно для Linux на архітектурах x86_64 та ARM64, а також для Windows. Характеристики продуктивності зберігаються на звичайних хмарних серверах, незалежно від операційної системи.

    Запуск Bun на AWS Lambda

    Bun може обслуговувати HTTP-бекенди через свій вбудований API Bun.serve() або через відомі фреймворки, такі як Express, Fastify та Hono. У середовищі AWS Lambda його швидке запускання при першому виклику дозволяє зменшити як час відгуку, так і витрати на виконання. Lambda не надає керованого середовища виконання Bun для вибору в консолі, тому команди зазвичай обирають один із трьох підходів:

    • Власний шар середовища виконання. Опублікуйте бінарний файл bun як шар Lambda та запустіть його на базовому образі Linux provided.al2023.
    • Образ контейнера. Скомпілюйте функцію з офіційного Docker-образу oven/bun, завантажте його до Amazon ECR та розгорніть як контейнер.
    • Компіляція з використанням Bun, виконання на Node. Використайте bun build, щоб зібрати код у один файл .js, а потім виконайте цей файл у керованому середовищі Node.js від Lambda.

    Третій варіант є найбільш консервативним: ви отримуєте швидке збирання проектів за допомогою Bun, зберігаючи при цьому підтримуваний AWS середовище виконання у продакшені. Ви відмовляєтесь від API, специфічних для середовища Bun, та переваги швидкого запуску, тож цей варіант підходить командам, які хочуть поступово переходити на Bun.

    Як Bun виконує TypeScript

    Ви запускаєте файли безпосередньо за допомогою команди bun, незалежно від того, чи це JavaScript чи TypeScript: bun index.js, bun index.ts або bun --watch index.ts для автоматичного перезапуску при зміні файлів.

    Коли ви запускаєте файл з розширенням .ts, транспілятор Bun видаляє анотації типів на льоту та робить це повністю в пам’яті. Він не створює на диску папку dist/, каталог .build/ чи тимчасові файли .js.

    Важливим застереженням є те, що видалення типів не є тим самим, що й їх перевірка. Щоб запуск відбувався миттєво, Bun не проводить статичної перевірки типів під час виконання, тому код із помилками типів все одно буде працювати. Перевірка типів залишається у вашому редакторі, такому як VS Code чи Cursor, а також у системах CI, де можна виконати команду bunx tsc --noEmit. Вважайте цей крок у системі CI обов’язковим.

    Поступова міграція існуючого проекту Node.js

    Оскільки Bun дотримується API Node та конвенцій npm, звичайний проект зазвичай мігрує з кількома змінами. Виконуйте це на гілці та наприкінці перевірте всю сукупність тестів.

    Крок 1: Видаліть старі модулі та файли lockfile

    Почніть із чистого дерева залежностей, щоб Bun самостійно вирішував усе: видаліть папку node_modules разом із будь-яким файлом блокування, який використовував проєкт — чи то package-lock.json, yarn.lock чи pnpm-lock.yaml.

    Видалення старого файлу блокування означає, що версії будуть знову визначатися на основі діапазонів у package.json, тож ви можете отримати новіші версії, ніж раніше. Якщо важлива точна відтворюваність, перевірте, чи може ваша версія Bun мігрувати існуючий файл блокування замість цього, та перегляньте відмінності між вирішеними версіями.

    Крок 2: Встановлення за допомогою Bun

    Виконайте команду bun install. Bun читає ваш існуючий package.json, завантажує пакети та створює файл bun.lock, який варто зберегти у коміті.

    Крок 3: Оновлення скриптів у package.json

    Замініть посилання на node, ts-node або tsx у розділі scripts:

    • start: "bun src/index.js" замінює "node src/index.js";
    • dev: "bun --watch src/index.ts" замінює "ts-node-dev src/index.ts";
    • test: "bun test" замінює "jest" або "vitest".

    Засіб для виконання тестів у більшості випадків сумісний з API Jest, але тести, які сильно залежать від специфічних механізмів імітації чи налаштувань Jest, можуть потребувати корекцій.

    Крок 4: Змінити визначення TypeScript

    Для проектів на TypeScript замініть визначення типів Node на визначення Bun, виконавши команди bun remove @types/node та bun add -d @types/bun.

    Потім оновіть запис types у полі compilerOptions файла tsconfig.json. У старіших посібниках там згадується "bun-types"; проте з поточним пакетом @types/bun цей запис зазвичай має значення "bun", тому перевірте документацію Bun для TypeScript у вашій версії.

    Крок 5: Замінити непідходящі модулі

    Стандартні модулі Node, такі як node:fs, node:path та node:http, працюють без змін. Проблеми зазвичай виникають із пакетами, які залежать від нативних біндингів на C++, і для деяких з них існують прості альтернативи:

    • замініть bcrypt на bcryptjs або @node-rs/bcrypt;
    • замініть sqlite3 на вбудований у Bun модуль bun:sqlite;
  • Видаліть виклики dotenv, оскільки Bun автоматично завантажує .env.
  • Ретельно обміркуйте цю останню зміну. Якщо той самий код все ще має працювати під Node, наприклад у вищезгаданій конфігурації Lambda лише для збірки, залиште dotenv або явно завантажуйте конфігурацію, щоб поведінка не відрізнялася між різними середовищами виконання.

    Основні висновки

    • Головна перевага Bun — це консолідація: один бінарний файл замінює менеджер пакетів, виконувач TypeScript, фреймворк тестів та інструмент для збирання проекту.
    • Його швидкість пояснюється реалізацією з урахуванням ефективного використання пам’яті та швидким запуском JavaScriptCore, а не тим, що Zig за своєю суттю швидший за C++.
    • Bun видаляє типи TypeScript з пам’яті, але ніколи їх не перевіряє, тому у процесах CI залишайте параметр tsc --noEmit.
    • У Lambda ви можете обрати між власним шаром, зображенням контейнера або збиранням проекту за допомогою Bun та його виконанням на керованому Node.js.
    • Міграція здебільшого є механічною, але слід стежити за повторним розв’язуванням проблем з файлом блокування, нативними додатками та будь-якою поведінкою, яка залежить від автоматичного завантаження файлу .env.