Перенос проекта Node.js на Bun: внутренние механизмы выполнения, Lambda и процесс миграции
Понять, что заменяет Bun в среде разработки Node.js, почему он запускается быстро, как он обрабатывает TypeScript и работает в AWS Lambda, а также как пошагово мигрировать проект.
Источники: Everything You Need to Know About Bun: A Beginner’s Guide, Migration, and Architecture
Типичный сервис на Node.js зависит от четырех или пяти отдельных инструментов до того, как сможет выполнить хотя бы одну строку бизнес-логики: менеджера пакетов, запускача TypeScript, фреймворка тестирования и инструмента для сборки. Bun объединяет все эти компоненты в один исполняемый файл, который выполняет функции среды выполнения, менеджера пакетов, инструмента для сборки и запуска тестов, при этом он предназначен стать прямой заменой Node.js. В этом руководстве объясняется, что именно заменяет Bun, откуда берется его скорость, как он обрабатывает TypeScript и развертывание без сервера, а также как перенести существующий проект на Bun без неожиданностей. Для более полного сравнения на основе тестов, включающего также 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работает в до 30 раз быстрее, чемnpm; скорость будет зависеть от проекта и состояния кэша. - Быстрый старт без подготовки. Процессы запускаются за миллисекунды, что особенно важно в безсерверных средах, где задержка старта взимается и ощущается пользователями.
- TypeScript без настройки. Файлы
.tsи.tsxзапускаются напрямую, без этапа сборки или скриптов загрузчика.
.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 и запустите его на базовом образе Linuxprovided.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: вместо
"node src/index.js"используется"bun src/index.js"; - dev: вместо
"ts-node-dev src/index.ts"используется"bun --watch src/index.ts"; - test: вместо
"jest"или"vitest"используется"bun test".
Инструмент запуска тестов в значительной степени совместим с 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.