12k
All articles

Представляем Nub, комплексный инструментарий для Node.js

Nub — Rust-утилита для Node.js: запускает TypeScript, скрипты, установку и версии Node на обычном Node, сохраняя lockfile и проверки безопасности.

OpenReplay Team
OpenReplay Team
Представляем Nub, комплексный инструментарий для Node.js

Nub — это консольный инструментарий для Node.js, написанный на Rust. Он транспилирует TypeScript, запускает скрипты из package.json, устанавливает зависимости и разворачивает нужные версии Node, после чего передаёт выполнение штатному бинарнику node, который уже зафиксирован в вашем проекте. Он дополняет Node, а не заменяет его, — и именно в этом всё его отличие от Bun или Deno.

Большинство команд, которые взвешивали Bun и Deno против Node, так и не продвинулись дальше первого вопроса: вы не меняете рантайм под боевым сервисом только потому, что где-то удобнее работать разработчику. Nub идёт другим путём, называя себя инструментарием на Rust, который оставляет ваш Node, ваш lock-файл и ваш пакетный менеджер на своих местах. Вот что это даёт и во что обходится попытка попробовать.

Ключевые выводы

  • Nub — это CLI на Rust, который надстраивает выполнение TypeScript, запуск скриптов, установку пакетов и управление версиями Node поверх штатного бинарника node, поэтому никакой новый рантайм аттестовать не нужно.
  • Собственная поддержка TypeScript в Node лишь удаляет аннотации и отвергает всё, что требует генерации кода, например enum’ы, parameter properties или namespace с runtime-кодом; загрузчик Nub вместо этого компилирует такие конструкции.
  • Установщик Nub построен по образцу pnpm и читает и записывает существующие lock-файлы npm, pnpm и bun прямо на месте, а lock-файлы yarn — только для чтения.
  • Защитные механизмы при установке не требуют настройки: build-скрипты зависимостей остаются заблокированными, пока вы их не одобрите, каждое новое разрешение зависимостей проверяется по OSV, а 24-часовой порог по возрасту релиза не пропускает совсем свежие версии.
  • Специфичных для Nub API нет, собственного lock-файла тоже нет, а nub.jsonc необязателен, поэтому удаление Nub возвращает проект к обычному Node.

Что такое Nub и чем он не является

Nub — это не четвёртый рантайм. Это единый бинарник, который стоит перед Node, выполняет работу, сейчас требующую tsx, nvm, npx и пакетного менеджера, а затем делает exec настоящего Node. На домашней странице механизм описан прямо: oxc компилирует ваши файлы в памяти внутри нативного аддона, а штатный бинарник node выполняет то, что получилось. Никакого отдельного рантайма под капотом нет, и раннер файлов принимает те же флаги, что и node.

В вашей целевой среде развёртывания ничего не меняется. Версия V8, C++ ABI, под который собирались ваши нативные модули, поверхность process, за которую цепляется ваша инструментация, — всё это тот же самый Node, который вы и так поставляли. Для расширенного режима требуется Node 18.19 или новее (Node 18 LTS) на macOS, Linux и Windows, каждая — и на x64, и на arm64.

Проект молодой. npm-пакет @nubjs/nub опубликован под MIT и всё ещё имеет версию до 1.0 — на момент последнего релиза это линия 0.9.x, причём новые версии выходят часто.

Как Nub выполняет TypeScript без шага сборки

Собственная поддержка TypeScript в Node удаляет типы, а не компилирует их. Аннотации заменяются пробелами, а всё, для чего пришлось бы генерировать JavaScript, отвергается. В документации Node перечислены эти случаи: enum’ы, namespace с runtime-кодом, parameter properties и алиасы import = вызывают ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX; декораторы не парсятся; а поскольку Node вообще не открывает tsconfig.json, алиасы paths не применяются. Более полный режим трансформации раньше был доступен за флагом --experimental-transform-types, но Node удалил этот флаг в версии 26, так что теперь стирание типов — единственный встроенный путь.

Эти исключения — ровно тот синтаксис, из которого состоит кодовая база на NestJS или TypeORM. Возьмём файл с enum’ом, parameter property и относительным импортом без расширения:

// invoice.ts
import { Model } from "./base"

enum Status { Draft, Sent, Paid }

export class Invoice extends Model {
  constructor(public status: Status = Status.Draft) {
    super()
  }
}

При обычном node invoice.ts enum и parameter property не поддаются стиранию, а у импорта нет расширения. При nub invoice.ts тот же файл выполняется без изменений. Nub передаёт каждый файл своему нативному аддону на компиляцию — именно поэтому enum, parameter property и импорт без расширения работают. Кроме того, он обходит ваш tsconfig.json и любую конфигурацию, которую этот файл extends, а затем передаёт алиасы paths собственному резолверу Node через resolve-хук module.registerHooks().

Декораторы поддерживаются только в одном варианте. В анонсирующем посте описаны legacy-декораторы experimentalDecorators — та форма, под которую написаны NestJS, TypeORM и Angular, вместе с emitDecoratorMetadata. Декораторы Stage 3, которые TypeScript 5 использует по умолчанию, отклоняются, потому что их трансформация всё ещё остаётся незакрытым пробелом в oxc. Раннер при этом генерирует инлайновые source maps, так что стек-трейсы указывают на ваш исходный код, а не на сгенерированный вывод. Эта деталь не косметическая: транспилированный TypeScript, потерявший source maps, выдаёт трейсы по коду, который никто не писал, а это регулярный источник впустую потраченного времени на разбор инцидентов.

Какие команды заменяет Nub

Единый бинарник Nub покрывает работу, сейчас размазанную по целой полке инструментов. Задокументированное соответствие замен прямолинейно:

Команда NubЗаменяет
nub <file>node, tsx, ts-node, dotenv-cli
nub run <script>npm run, pnpm run, yarn run
nubxnpx, pnpm dlx, pnpm exec, yarn dlx
nub installnpm, pnpm, yarn
nub watchnodemon, node --watch, tsx watch
nub nodenvm, fnm, n, volta
nub pmcorepack

Эта таблица не исчерпывает всю поверхность. В README также описан nubr — единая команда, которая запустит файл, скрипт из package.json или бинарник из node_modules/.bin, пробуя их именно в таком порядке. Она поставляется и отдельно, как @nubjs/runner, для проектов, куда нельзя установить бинарник.

Важное свойство в том, что всё это независимо. Переход на раннер файлов не обязывает вас переходить на установщик, а замена скрипта dev с tsx watch src/server.ts на nub watch src/server.ts оставляет package.json обычным npm-совместимым манифестом. Заявления о скорости проект подкрепляет собственными бенчмарками: в 24 раза быстрее pnpm run при запуске скриптов, в 19 раз быстрее npx при выполнении бинарников и в 18 раз быстрее pnpm install. Парные замеры в README дают 14,7 мс на запуск скрипта против 329,9 мс у npm, и 171 мс на «тёплую» установку с фиксированным lock-файлом против 3193 мс у pnpm — оба замера на macOS. Второй бенчмарк установки, выполненный через hyperfine на раннере ubuntu-latest на дереве из 1168 пакетов, показывает 346 мс у Nub против 3453 мс у pnpm.

Пакетный менеджер: по образцу pnpm и с сохранением lock-файлов

Установщик Nub не вводит собственный формат lock-файла. Он определяет, какой пакетный менеджер проект уже использует — по package.json#packageManager или по тому lock-файлу, который найдёт, — а затем работает в режиме совместимости и соблюдает конфигурационные файлы и переменные окружения этого инструмента. Сам CLI построен по образцу pnpm, поэтому nub install, nub add -E -D react, nub remove, nub update и nub ci ведут себя так, как подсказывает мышечная память.

Конкретно по lock-файлам: lock-файлы npm, pnpm и bun читаются и записываются на месте, а lock-файлы yarn — только для чтения. Ничего не конвертируется, и второй lock-файл в diff не появляется. Для команды на pnpm именно этот вопрос решает, вообще ли стоит рассматривать инструмент.

Разрешение версий Node без nvm

nub node определяет, какую версию Node ожидает проект, и разворачивает её по требованию. Версия берётся из .node-version, .nvmrc или package.json#engines, а отсутствующая версия скачивается и кэшируется автоматически; доступны и явные команды: nub node install 26, nub node ls, nub node pin 26 и nub node uninstall 22. Всё это делается без shell-хуков и без переписывания вашего PATH — а именно эта часть nvm обычно ломается в CI и в неинтерактивных оболочках.

Настройки защиты цепочки поставок по умолчанию и отсутствие привязки

Защитные механизмы Nub на этапе установки включены без какой-либо настройки. Четыре из них задокументированы. Build-скрипты зависимости не запускаются, пока вы не одобрите этот пакет. Каждое новое разрешение зависимостей проверяется по OSV на известные вредоносные версии. Версия, потерявшая свидетельства доверия к публикации, которые были у более раннего релиза, отклоняется сразу. А minimumReleaseAge по умолчанию равен 24 часам — то же окно, что использует pnpm, — поэтому версия, опубликованная несколько минут назад, не попадёт в ваше дерево зависимостей. В анонсирующем посте добавлено, что транзитивная зависимость, разрешающаяся в URL вида git+, file: или прямой tarball, отклоняется, а не скачивается молча. Если вы уже прорабатывали оборонительную стратегию против атак на цепочку поставок npm, то это тот же чек-лист, только как поведение по умолчанию, а не как .npmrc, который вы сопровождаете.

Вторая половина — заявление об обратимости. Nub не добавляет API, которые нужно импортировать, не пишет собственный lock-файл и считает nub.jsonc необязательной конфигурацией, а не обязательным требованием. Удалите бинарник — и проект будет работать на обычном Node с тем инструментарием, который был раньше, потому что исходный код изначально нигде не ссылался на Nub.

Кому стоит попробовать Nub, а кому нет

Стоит попробовать, если вы запускаете TypeScript через tsx или ts-node, держите под рукой nvm для фиксации версий и предпочли бы не тратить квартал на аттестацию нового рантайма ради того, чтобы от этого избавиться. Начните с раннера файлов на одном сервисе, установщик не трогайте и посмотрите, исчезнет ли трение с шагом сборки из класса «enum’ы и декораторы». Пропустите — пока что — если вам нужен зафиксированный, скучный и предсказуемый тулчейн для регламентированного релизного процесса, потому что проект до версии 1.0, выпускающий релизы с интервалом в несколько дней, таковым ещё не является. Цена проверки — npm install -g @nubjs/nub и одна команда над файлом, который у вас уже есть.

Часто задаваемые вопросы

Как запустить файл через Nub вообще без каких-либо дополнений?

Используйте режим совместимости: передайте --node для одного вызова или установите NODE_COMPAT в 1, true или yes, чтобы охватить всё дерево процессов. В этом режиме Nub не применяет ничего: нет load-хука, нет preload, нет внедрения флагов и нет загрузки .env. При этом он всё равно определяет, какую версию Node фиксирует проект, и при необходимости устанавливает её, так что ваш код выполняется в чистом виде на нужной версии. Это удобно, чтобы отличить баг Nub от бага Node.

Какие платформы и версии Node поддерживает Nub?

Nub поставляется в виде предсобранных бинарников на Rust для Linux, macOS и Windows, на x64 и arm64, и при установке подтягивает подходящий для вашей платформы N-API-аддон. Расширенным режимам нужен Node 18.19 или новее, потому что именно там впервые появляется API loader-хуков, на котором держится транспиляция при импорте. На чём-то более старом расширенная команда завершится с ошибкой, в которой указана минимальная версия и есть отсылка к режиму совместимости.

Почему установка падает с ERR_NUB_ALLOW_BUILDS_RENAMED?

В Nub 0.9.0 верхнеуровневый allowlist сборок в package.json был переименован из allowBuilds в allowScripts, чтобы совпадать с полем, которое читает npm 12. Проект, всё ещё содержащий корневую карту allowBuilds, отклоняется с этой ошибкой, а не получает предупреждение, так что исправление — переименовать ключ. allowBuilds от pnpm — это другая настройка, и её не трогают, где бы она ни находилась: в pnpm-workspace.yaml или в package.json#pnpm.

Можно ли использовать nubx, не меняя пакетный менеджер?

Да. nubx находит локально установленный CLI в node_modules/.bin независимо от того, что его туда положило, поэтому он работает в проекте, установленном через npm, pnpm, yarn или bun, без какой-либо миграции. Он принимает флаги pnpm exec под теми же именами, а nub dlx повторяет pnpm dlx вплоть до shell-режима, так что уже имеющиеся у вас команды переносятся как есть.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.