12k
All articles

Nub の紹介 — オールインワンな Node.js ツールキット

NubはRust製のNode.jsツールキットで、標準のNode上でTypeScript、スクリプト、依存関係の導入、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 は別の道を選び、自らを あなたの Node、ロックファイル、パッケージマネージャーをそのまま残す Rust ツールキットと称しています。それによって何が得られ、試すのに何がかかるのかを見ていきましょう。

要点

  • Nub は Rust 製の CLI で、TypeScript の実行、スクリプトのディスパッチ、パッケージのインストール、Node のバージョン管理を素の node バイナリの上に重ねます。そのため、新たに検証すべきランタイムは存在しません。
  • Node 自身の TypeScript サポートは型注釈を削除するだけで、enum、パラメータープロパティ、ランタイムコードを含む namespace など、コード生成を必要とする構文はすべて拒否します。Nub のローダーはそれらをコンパイルします。
  • Nub のインストーラーは pnpm 型で、既存の npm、pnpm、bun のロックファイルをその場で読み書きします(yarn のロックファイルは読み取り専用)。
  • インストール時の防御機構は設定不要です。依存関係のビルドスクリプトは承認するまでブロックされ、新規解決はすべて OSV に照合され、24 時間のリリース経過時間ゲートが出たばかりのバージョンを締め出します。
  • Nub 固有の API も Nub 独自のロックファイルも存在せず、nub.jsonc も任意です。そのため Nub を外せばプロジェクトは素の Node に戻ります。

Nub とは何か、そして何ではないのか

Nub は 4 つ目のランタイムではありません。Node の前段に位置する単一のバイナリであり、現在 tsx、nvm、npx、そしてパッケージマネージャーを必要としている作業をこなしたうえで、本物の Node を exec します。公式サイトはその仕組みを端的に述べています。oxc がネイティブアドオンの内部でファイルをメモリ上でコンパイルし、出てきたものを素の node バイナリが実行するというものです。下層に別のランタイムが存在することはなく、ファイルランナーは node と同じフラグを受け付けます。

デプロイ先については何も変わりません。V8 のバージョン、ネイティブモジュールがビルド対象とした C++ ABI、計測ツールがフックする process の表面、そのすべてがこれまで出荷してきた Node のままです。拡張パスには Node 18.19 以降(Node 18 LTS)が必要で、macOS、Linux、Windows のそれぞれで x64 と arm64 の両方に対応しています。

プロジェクトはまだ初期段階です。npm パッケージ @nubjs/nub は MIT ライセンスで公開されており、最新リリース時点では 0.9.x 系の pre-1.0 で、新バージョンが頻繁にリリースされています。

Nub はどうやってビルドステップなしに TypeScript を実行するのか

Node 自身の TypeScript サポートは、型をコンパイルするのではなく削除します。型注釈は空白に置き換えられ、JavaScript の生成が必要となるものはすべて拒否されます。Node のドキュメントは該当ケースを列挙しています。enum、ランタイムコードを含む namespace、パラメータープロパティ、import = エイリアスはいずれも ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX を発生させ、デコレーターはパースに失敗します。また Node は tsconfig.json を一切開かないため、paths エイリアスは適用されません。より完全な変換モードは以前 --experimental-transform-types の背後にありましたが、Node はバージョン 26 でこのフラグを削除したため、現在は型消去のみが組み込みの経路となっています。

これらの除外項目は、まさに NestJS や TypeORM のコードベースを構成している構文そのものです。enum、パラメータープロパティ、拡張子なしの相対インポートを使ったファイルを見てみましょう。

// 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 とパラメータープロパティは消去不可能であり、インポートには拡張子がありません。nub invoice.ts では、同じファイルが手を加えずに実行されます。Nub はすべてのファイルをネイティブアドオンに渡してコンパイルします。だからこそ enum、パラメータープロパティ、拡張子なしのインポートがすべて機能するのです。さらに tsconfig.json と、そのファイルが extends している設定を辿り、paths エイリアスを module.registerHooks() の resolve フック経由で Node 自身のリゾルバーに渡します。

デコレーターのサポートは 1 種類のみです。ローンチ記事が扱っているのは レガシーな experimentalDecorators、つまり NestJS、TypeORM、Angular が前提としている形式で、emitDecoratorMetadata も併せて利用できます。TypeScript 5 がデフォルトで採用する Stage 3 デコレーターは拒否されます。その変換が oxc でまだ未対応のギャップとして残っているためです。ランナーはインラインのソースマップを出力するので、スタックトレースは生成されたコードではなくソースを指します。この最後の点は見た目の問題ではありません。ソースマップを失ったトランスパイル済み TypeScript は、誰も書いていないコードに対するトレースを生み出し、トリアージの時間を無駄にする原因として繰り返し現れます。

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 の bin をこの順で試して実行する単一コマンドです。バイナリをインストールできないプロジェクト向けに、@nubjs/runner として単体でも提供されています。

重要な性質は、これらが互いに独立している点です。ファイルランナーを採用したからといってインストーラーまで採用する義務はなく、dev スクリプトを tsx watch src/server.ts から nub watch src/server.ts に差し替えても、package.json は通常の npm 互換マニフェストのままです。プロジェクトは速度についての主張を自前のベンチマークで報告しています。スクリプトディスパッチは pnpm run より 24 倍、bin の実行は npx より 19 倍、pnpm install より 18 倍高速とのことです。README の対比計測では、スクリプトディスパッチが npm の 329.9 ms に対して 14.7 ms、ウォーム状態の frozen install が pnpm の 3193 ms に対して 171 ms で、いずれも macOS で計測されています。もう 1 つのインストールベンチマークは、ubuntu-latest ランナー上で hyperfine を使い 1,168 パッケージのツリーに対して実行され、Nub が 346 ms、pnpm が 3453 ms という結果です。

パッケージマネージャー: pnpm 型でロックファイルを保持する

Nub のインストーラーは新しいロックファイル形式を導入しません。package.json#packageManager あるいは見つかったロックファイルから、プロジェクトがすでに使っているパッケージマネージャーを判別し、compat モードで動作してそのツールの設定ファイルと環境変数を尊重します。CLI 自体は pnpm 型なので、nub install、nub add -E -D react、nub remove、nub update、nub ci は body に染みついた操作感のとおりに振る舞います。

ロックファイルについて具体的には、npm、pnpm、bun のロックファイルはその場で読み書きされ、yarn のロックファイルは読み取り専用です。変換は行われず、差分に 2 つ目のロックファイルが現れることもありません。pnpm を使っているチームにとっては、そもそも評価対象になり得るかどうかを決める問いがここです。

nvm なしでの Node バージョン解決

nub node はプロジェクトが期待する Node のバージョンを解決し、必要に応じてプロビジョニングします。バージョンは .node-version、.nvmrc、package.json#engines から取得され、存在しないバージョンは自動的にダウンロードされてキャッシュされます。明示的なサブコマンドも用意されています: nub node install 26、nub node ls、nub node pin 26、nub node uninstall 22。これをシェルフックなしで、PATH を書き換えることなく行います。PATH の書き換えは、CI や非対話シェルで nvm が壊れがちな部分です。

サプライチェーンのデフォルト設定と、ロックインの不在

Nub のインストール時防御機構は設定なしで有効です。ドキュメントには 4 つが記載されています。依存関係のビルドスクリプトは、そのパッケージを承認するまで実行されません。新規解決はすべて OSV に照合され、既知の悪意あるバージョンをチェックします。以前のリリースが持っていた公開の信頼証跡を失ったバージョンは、そのまま拒否されます。そして minimumReleaseAge は pnpm と同じ 24 時間がデフォルトなので、数分前に公開されたバージョンがツリーに入り込むことはありません。ローンチ記事はさらに、推移的依存が git+、file:、あるいは素の tarball URL に解決される場合、黙って取得するのではなく拒否すると述べています。すでに npm サプライチェーン攻撃に対する防御体制を整えたことがあるなら、これはそのチェックリストが、自分で保守する .npmrc ではなくデフォルトとして提供されているものだと言えます。

可逆性の主張がもう半分です。Nub はインポートすべき API を追加せず、独自のロックファイルも書かず、nub.jsonc を必須ではなく任意の設定として扱います。バイナリをアンインストールすれば、プロジェクトは以前のツールチェーンとともに素の Node 上で動きます。ソースコードがそもそも Nub を参照していないからです。

誰が Nub を試すべきで、誰が試すべきでないか

tsx や ts-node で TypeScript を実行し、バージョン固定のために nvm を手元に置いていて、そこから抜け出すために新しいランタイムの検証に四半期を費やしたくはない、という人は試してみてください。まずは 1 つのサービスでファイルランナーから始め、インストーラーには手を付けず、enum とデコレーターに起因するビルドステップの摩擦が消えるかどうかを確かめましょう。一方で、規制対象のリリースプロセス向けに固定された退屈なツールチェーンが必要なら、今のところは見送りましょう。数日おきにリリースを出している pre-1.0 のプロジェクトはまだそこに至っていません。確かめるためのコストは npm install -g @nubjs/nub と、すでに手元にあるファイルに対する 1 コマンドだけです。

FAQ

拡張を一切行わずに Nub 経由でファイルを実行するには?

互換モードを使います。単発の実行なら --node を渡し、プロセスツリー全体に適用するなら NODE_COMPAT を 1、true、yes のいずれかに設定します。このモードでは Nub は何も適用しないため、ロードフックもプリロードもフラグの注入も .env の読み込みもありません。それでもプロジェクトがピン留めしている Node を判別し、必要であればインストールするので、コードは正しいバージョン上で素の状態で動作します。これにより、Nub のバグと Node のバグを切り分けるのに役立ちます。

Nub はどのプラットフォームと Node バージョンをサポートしていますか?

Nub は Linux、macOS、Windows 向けに x64 と arm64 のビルド済み Rust バイナリを提供し、インストール時にプラットフォームに対応する N-API アドオンを取得します。拡張モードには Node 18.19 以降が必要です。import 時トランスパイルのパスを支えるローダーフック API が最初に登場したのがこのバージョンだからです。それより古い環境では、拡張コマンドは下限バージョンを示し互換モードを案内するエラーで停止します。

なぜインストールが ERR_NUB_ALLOW_BUILDS_RENAMED で失敗するのですか?

Nub 0.9.0 は package.json のトップレベルのビルド許可リストを allowBuilds から allowScripts に改名し、npm 12 が読むスロットに合わせました。ルートに allowBuilds マップを残しているプロジェクトは警告ではなくこのエラーで拒否されるため、対処はキーの名前を変えることです。pnpm の allowBuilds は別の設定であり、pnpm-workspace.yaml にあっても package.json#pnpm の下にあってもそのまま残されます。

パッケージマネージャーを切り替えずに nubx を使えますか?

使えます。nubx は node_modules/.bin にローカルインストールされた CLI を、それを置いたのが何であれ見つけ出すので、npm、pnpm、yarn、bun のいずれでインストールされたプロジェクトでも、何も移行せずに動作します。pnpm exec のフラグを同じ名前で受け付け、nub dlx は shell モードに至るまで pnpm dlx を踏襲しているため、既存のコマンドラインをそのまま持ち込めます。

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.