🏃

JavaScript Runtimes (Node.js, Deno, Bun)

2023/09/24に公開

はじめに

Bun 1.0 がリリースされたので、主要な3つの JavaScript Runtime のそれぞれの特徴をまとめる目的で書きました。

情報は 2023年09月時点のものです。また筆者はそれぞれの JavaScript Runtime に精通しているわけではないので、不足や間違った情報があるかもしれませんので、その点ご了承ください。不足や間違いがありましたら、コメントいただけると嬉しいです。

JavaScript Runtime とは

JavaScript を実行する環境全般を指します。
この記事では、ブラウザを除く以下の3つの JavaScript Runtime を対象にしています。

JavaScript Runtime のシェア

State of JavaScript 2022 の調査結果によると、Node.js が圧倒的なシェアを持っているようです。

Node.js と比較すると Deno や Bun のシェアはまだ小さいですね。

State of JavaScript 2022

State of JavaScript 2022: Other Tools

JavaScript/TypeScript のシェア

ついでに言語そのもののシェアも気になったので調べてみました。

GitHub の調査によると、JavaScript は1位を直近8年間キープしており、TypeScript は2020年から4位を維持しています。

The top programming languages

The top programming languages | The State of the Octoverse

Node.js

Node.js とは

サーバーサイドや開発環境で最も使われている JavaScript の実行環境です。

一般にブラウザ以外で JavaScript を使うといえば、ほとんどの方は Node.js を思い浮かべるのではないでしょうか。以降で紹介する Deno, Bun の特徴は、基本的に「Node.js と比較した」差分というイメージになります。そのぐらい、Node.js は最もスタンダートな JavaScript Runtime と言えるでしょう。

Node.js はクロスプラットフォームに対応したオープンソースのJavaScript実行環境です。

https://nodejs.org/ja

どこを最初のリリースとするかは難しい(後述の「Node.js v4 までの歴史」も参照してください)ですが、 v0.1.0 のコミットは 2009-06-30、v4.0.0 のコミットは 2015-10-08 でした。

Node.js が生まれた背景

ざっくり言うと、2007年頃の C10K問題 に対する解決策として イベントループ を採用した Node.js が誕生したようです。

こちらのスライド によれば、EventLoop を実現するためのライブラリのもととなった libebb は(C10K という直接の記載こそないですが)その辺りを意識されて作られていたように見えます。EventLoop については JavaScriptランタイム事情 2022冬 - Techtouch Developers Blog動画で分かる!ブラウザと Node.js の Event Loop 解説 などの解説がわかりやすかったです。

当然Webサーバーの用途として使えますが、それ以外でもトランスパイルやバンドル、テストランナーなど、様々な用途で使われています。

Node.js v4 までの歴史

Node.js は当初 v0 系としてリリースされていましたが、色々とあって io.js として v1〜v3 がリリースされたようです。その後、io.js と Node.js が合流して Node.js v4 以降がリリースされた、という流れのようです。

詳しくは「どうしてこうなった? Node.jsとio.jsの分裂と統合の行方。これからどう進化していくのか? - Speaker Deck」 というスライドがわかりやすかったので、こちらも参照してみてください。

Node.js のリリース

nodejs/Release というリポジトリの README を見ると、リリーススケジュールや方法に関して詳細が書かれています。
簡単に概要をまとめると、以下のような感じのようです。

  • 以下の3つのフェーズがある
    • Current: main ブランチから破壊的変更ではない変更を積極的に取りいれる
    • Active LTS: LTS チームによって、適切かつ安定していると判断された変更を取り入れる
    • Maintenance: 基本的に重要なバグ修正とセキュリティ更新のみを取り入れる
  • メジャーバージョンが偶数のもののみ LTS に昇格する(奇数のものはならない)
  • メジャーバージョンが偶数のバージョンは4月に、奇数のバージョンは10月にリリースされる

しっかりとしたリリーススケジュールや、役割に応じてチームが複数組まれているなど、かなりしっかりした体制が取られているな、と私は感じました。

余談ですが、Node.js 16 の LTS サポートが 2024-04 から 2023-09-11 に変更される ということがありました。
理由として、Node.js 16 は(本当は OpenSSL3 にしたかったがリリース日の関係で)OpenSSL1.1.1 に依存しており、そのサポートが 2023-09-11 までであるためでした。他の選択肢も検討されたようですが、LTS 終了日の変更(短縮)が最もリスクが低いと判断されたようです。

こういう事態にも適切に議論をして対応を決めているんだなぁ、と感心しました。

Deno

Deno は Node.js の作者でもある Ryan Dahl 氏が作られた JavaScript Runtime です。

Deno (/ˈdiːnoʊ/, pronounced dee-no) is a JavaScript, TypeScript, and WebAssembly runtime with secure defaults and a great developer experience. It's built on V8, Rust, and Tokio.

Deno Runtime Quick Start | Deno Docs

Deno は JavaScript、TypeScript、WebAssembly ランタイムであり、セキュアなデフォルト設定と優れた開発者エクスペリエンスを備えている。V8、Rust、Tokioをベースに構築されている。

[DeepL による翻訳]

特徴としては、セキュアであることと、開発者体験を重視していることだと思います。

2018年に発表され、v1.0.0 は 2020-05-14 にリリースされました。v1.0 公開時のブログもありました。

Node.js に関する10の反省

Node.js の作者である Ryan Dahl 氏が、「Design Mistake in Node」という発表を JS Conf EU (2018年) で行いました。その中で Node.js に関する10の反省と、それを改善した新しい JavaScript Runtime "Deno" が発表されました。

...。具体的には、以下の点が指摘されました。

  • APIの設計において非同期処理に使うpromiseを使用しなかったこと
  • 古いGYPビルドシステムを採用したこと
  • パッケージ管理において設定ファイルのpackage.jsonとnode_modulesを採用したこと
  • モジュールのインポートで拡張子を除外したこと
  • index.jsによりモジュール依存関係の解決を採用したこと
  • JavaScriptのコアエンジンV8によるサンドボックス環境を破壊するような実装をしたこと

世界のプログラミング言語(34) Node.jsに関する10の反省点から生まれたJS実行エンジンDeno | TECH+(テックプラス)

セキュリティ

Deno はデフォルトで様々なアクセスを禁止しており、実行時に以下のようなフラグを付ける ことで許可することができるようになっています。

  • --allow-read: ファイルシステム(読み込み)
  • --allow-write: ファイルシステム(書き込み)
  • --allow-net: ネットワークアクセス
  • --allow-env: 環境変数
  • --allow-sys: ユーザーのOSに関する情報
  • --allow-run: サブプロセスの実行
  • --allow-ffi: ダイナミック・ライブラリのロード
  • --allow-hrtime: 高解像度の時刻

一部分のみ許可することも可能です。(例: ./sample.file というファイルのみ読み取りアクセスを許可する場合)

$ deno run --allow-read="./sample.file" datetime.ts 

また開発時など安全なコンテキストで実行できる場合は、--allow-all または -A というフラグを付けることで、すべての権限を許可することもできます。

$ deno run --allow-all datetime.ts 
# ot
$ deno run -A datetime.ts 

一時期 Vue.js などで使われている node-ipc というライブラリが話題になったりもしました。

『このコードはロシアあるいはベラルーシのIPを持つユーザーを対象として、ファイルの内容を消去してハートの絵文字に上書きしてしまうというものでした。』

オープンソースのnpmパッケージ「node-ipc」にロシア在住の開発者を標的にした悪意のあるコードがメンテナーによって追加される - GIGAZINE

適切に権限を設定しておけば、こういった事象を防ぐことができます。

TypeScript, JSX サポート

Deno は TypeScript, JSX を外部ライブラリ(tsc, ts-node など)の依存なく使用することができます。

ツール内蔵

Deno は フォーマッターやテストランナーなどのツールが含まれており、コマンドで簡単に実行できるようになっています。

$ deno fmt  # フォーマッター (例: prettier)
$ deno lint # リントツール (例: ESLint)
$ deno test # テストランナー (例: jest)
$ deno check # 型チェック (例: `tsx --noEmit`)

Denoとは開発者体験(DX)にフォーカスしたJavaScript runtimeです。

https://techfeed.io/entries/638d553d9317356b43b0c428

ECMAScript & Web互換API

Deno は、ECMAScript にのみ準拠しています。Node.js などで使われている CommonJS のモジュールシステムは使用できません。

Web互換API もサポートしており fetch crypto localStorage などの API を使用できます。 こちらのページ でサポートしている API を見ることができるようです。

独自のモジュールシステム

Deno は npm ではない独自のモジュールシステムを採用しています。独自の、というよりは ESModules に準拠した方法で、URL を用いてインポートが行えるといったほうが良いかもしれません。ソースコード内で直接URLを指定することもできますし、 deno.jsonc ファイルなどで指定することもできます。

ただし2022年8月に 大きな方針転換 があり(2019年頃から議論は始まっていたようです)、 Node.js APInpm、更に package.json もサポートし、これまでの方法でも使うことができるようになってきています。(ただし前述の通り CommonJS は使えないというデメリットがあり、その場合は esm.sh などを経由するなどの工夫が必要になります)

補足: CommonJS について

最近では ECMAScript Modules (ESModules) が主流で、CommonJS が積極的に使われるケースは少ないと筆者は感じています。
それでも CommonJS で作られた過去の資産は多く、それらが使えないというのはデメリットにもなりえます。

Deno は、CommonJS is hurting JavaScript というブログの投稿があり、2009年当時は CommonJS の必要性があり使われてきたが、今日では CommonJS には核心的な課題や ESModules との相互運用に課題があり、ESModules を使うことを推進しているようです。

ちなみに、私が実際に使っている Node.js のプロジェクトで deno で起動できるか試してみましたが、以下のように CommonJS に起因するエラーが発生しました。

$ deno --version
deno 1.37.0 (release, aarch64-apple-darwin)
v8 11.8.172.3
typescript 5.2.2

$ deno task "<TASK_NAME>"
...
error: Uncaught (in promise) ReferenceError: require is not defined

npm サポート

Deno は npm をサポートしており、npm ライブラリを使用することができます。
以下のように記述するだけで、npm ライブラリを import できます。

import express from "npm:express@4";

補足: npm のインポート方法

Issue を見て、Deno の npm サポートは考えられているなぁ、と思ったのでメモ。


The npm: specifiers are simply another URL. This does not violate any standard.

https://github.com/denoland/deno/issues/13703#issuecomment-1044717403

この npm:{package} は URL としてパース可能で npm: というスキーマをもつURLとして扱えます。

const url = new URL('npm:express@4');
console.log(url.protocol); // => npm:

なので npm を特別扱いするのではなく、これまで通り URL によるインポートに npm: というスキーマを追加した、という位置づけになっているようです。

Deno KV

Deno KV は Deno に組み込まれた Key-Value Database です。
現時点ではベータ版なので --unstable フラグが必要です。

Deno KV Quick Start | Deno Docs を参考に簡単なコードを作ってみました。

const kv = await Deno.openKv();

await kv.set(["key", 1], { data: "2618a3e8-bb5a-41d9-b4d9-40adaf8cc397" });
await kv.set(["key", 2], { value: "48e1e1f2-b748-4f52-8d09-d1a11f5674b7" });

console.log('key-1:', await kv.get(["key", 1]));
console.log('key-2:', await kv.get(["key", 2]));
$ deno run --unstable datetime.ts 
key-1: {
  key: [ "key", 1 ],
  value: { data: "2618a3e8-bb5a-41d9-b4d9-40adaf8cc397" },
  versionstamp: "000000000000000b0000"
}
key-2: {
  key: [ "key", 2 ],
  value: { value: "48e1e1f2-b748-4f52-8d09-d1a11f5674b7" },
  versionstamp: "000000000000000c0000"
}

set() 時に有効期限を設定することも可能なようです。
性能次第ですが、キャッシュやセッションなどでも使いやすいかもしれませんね。

次で説明する Deno Deploy でも(まだベータ版ですが)同じコードで実行できるらしく、非常に魅力的だなと感じました。

Deno Deploy

Deno Deploy は、グローバルに展開するサーバーレスな JavaScript の実行サービスです。GitHub リポジトリと連携も可能で、容易に Web サービスがリリースができます。グローバルに配信され、ユーザーに一番近いエッジロケーションで実行される様になっています。配信されているリージョンは こちらのドキュメント にあります。

有料プランもありますが、個人開発レベルであれば無料で使うことができます

npm サポートも(まだベータ版ですが)追加されましたので、npm パッケージを使ってサービスを提供することもできるようになります。
ただし package.json はおそらく対応していないようなので、Node.js などのプロジェクトをちょっと手直しして動かす、などというのは現時点では難しそうです。

Deno Land Inc.

Deno Land Inc. という会社が設立され、Deno の開発が進められています。

開発体制の充実だけであれば基金や財団といった形でも実現できたはずです。おそらく、ビジネスを実現することこそ「Deno Company」設立の大きな目的なのでしょう。

Denoの作者ライアン・ダール氏らが「Deno Company」を立ち上げ。Denoの開発推進と商用サービスの実現へ - Publickey

Deno Deploy のようなサービスを提供できるのも会社化されているためとも言えるかもしれませんね。

他ツールとの連携

最近では、Jupyter notebook とのインテグレーションが v1.37 で入るようです。
他のエコシステムとの連携による差別化も重視しているのかもしれないな、と私は思いました。

Jupyter notebook がインストールされていれば、以下のコマンドを実行するだけでインストールされます。

$ deno jupyter --unstable # 現時点では `--unstable` が必要

Deno が Jupyter notebook でも使えるようになります。

Jupyter notebook

Bun

Bun is a fast, all-in-one toolkit for running, building, testing, and debugging JavaScript and TypeScript, from a single file to a full-stack application. Today, Bun is stable and production-ready.

https://bun.sh/blog/bun-v1.0

Bunは、単一のファイルからフルスタックのアプリケーションまで、JavaScriptとTypeScriptを実行、ビルド、テスト、デバッグするための高速でオールインワンのツールキットです。現在、Bunは安定しており、本番環境にも対応している。

[DeepLでの翻訳]

高速性と実行に必要なすべてが揃っていることを強調している、JavaScript Runtime です。

2022-07-06 に最初のアナウンスが Twitter (現: X) で行われ、v1.0.0 は 2023-09-08 にリリースされました。v1.0 公開時のブログもありました。

Node.js 互換

Bun は Node.js との互換性を重視しており、多くの Node.js のアプリケーションをそのまま動かすことができます。

package.json もサポートしており、npm ライブラリも使用できます。Node.js との互換性の状況は こちらのページ にあります。

また CommonJS と ESModules の両方にも対応しています。

パフォーマンス

Bun は実行速度を強く押し出しており、ホームページのファーストビューにベンチマーク結果を出しています。

benchmark

TypeScript & JSX Support

Deno 同様に TypeScript, JSX をサポートしています。

Oven

Deno と同じく、Bun は Oven という会社を設立し開発されています。Oven で Bun を焼く、というちょっと洒落た感じになっていますね。

ちなみに Bun の発表後に Oven 社ができた ので、最初は Bun が Oven を作るみたいな構図でした。(個人的にちょっとおもしろいと感じてました)

考察

比較表

ある程度特徴として比較できるものを表でまとめます。
全機能を網羅することを目的としているのではなく、比較しやすい点をピックアップしている点にご注意ください。

Node.js Deno Bun
CommonJS Support - Support
ECMAScript Support Support Support
Initial Release 2015-10-08 (v4.0) 2020-05-14 (v1.0) 2023-09-08 (v1.0)
TypeScript & JSX - Support Support
Test Runner v20~
Linter - -
Formatter - -
Permission Experimental Support -
Managed Service - Deno Deploy -
OS macOS, Linux, Windows macOS, Linux, Windows macOS, Linux (Windows *1)

*1 Bun 1.0 リリース時に Windows が近々サポートされるとアナウンスされていました。現在は Experimental です。

2023-10-01 追記

Deno vs. Bun vs. Node.js: A Feature Comparison という記事によくまとまっている比較表がありましたので追記します。

Deno から Node.js への影響

私の調べた中で影響を与えていそうだなと感じるものをピックアップします。

余談

Deno が Node.js の R&D (Deno で実装した機能を Node.js が取り込んで行くようなイメージ)になることを Ryan Dahl は恐れているらしいです。

TechFeed Experts Night#8 〜 JavaScriptランタイム戦争最前線 - TechFeed のアフタートークで聞きました)

Bun から Deno への影響

こちらも、私の調べた中で影響を与えていそうだなと感じるものをピックアップします。

実行速度

Deno は元々意識されていたが、更に高まった感じがします。

進化するDeno in 2022 - npm互換性、パフォーマンス、開発者体験の向上など - TechFeed でも出ていました。
また 同イベント のアフタートークで裏話として Unsafe Rust を使って更に高速化をしている部分もあるという話を聞きました。

npm サポートなど

Bun という Node.js との互換性を重視した JavaScript Runtime が出てきたことで、Deno も npm サポートなどの互換性を強化している と私は感じました。

各 JavaScript Runtime で使われている技術の違い

  • Node.js: C++, V8
  • Deno: Rust, V8
  • Bun: Zig, JavaScriptCore (Webkit)

Node.js と Deno はどちらも内部で V8 (Chrome などで使われている JavaScript エンジン)を使っているので、JavaScript の実行速度はほぼ同じで、ネットワークやファイルシステムへのアクセスなどでは差が出るのでは、と想像できます。

Bun は JavaScriptCore という Webkit の JavaScript エンジンを使用しています。これは Safari などで使われています。こちらは、V8 と違うので JavaScript の実行速度自体にも違いが出るかもしれません。

実行速度

色々な記事を調べている中で、実行速度に関して概ね以下のようになりそうな認識です。

  1. Bun
  2. Deno
  3. Node.js

ただ、実行速度に関する情報を調べている中で、そもそもベンチマーク自体が不正確なのではないか、という話もありました。

マイクロベンチマークしたら3倍速かったという話も見かけますが、これはソースコードのある部分の処理が局所的に3倍速いということです。ある処理はNode.jsが最速で、ある処理はDenoが最速で、ある処理はBunが最速という状況らしいので、ソースコード全体で見ると実行速度の劇的な差はそんなにない気がします。

Node.js と Deno と Bun のどれを使えばいいのか - Qiita

また、それ以前に、JavaScript Runtime を使う人によって用途も様々なので、全てを一括りにしてどれが一番早いかというのは難しいかもしれません。
結局のところ、自分が使っているアプリケーションで計測してみるというのが一番良いかもしれません。(互換性の問題で正確に測れない場合もあるかもしれませんが)

ちなみに、Bun は正規表現が速いらしいです。

いくつか項目があるのですが、多くの場合RegExpRouterが速く、全てをあわせたものだとかなり差をつけて1番でした。特にBunは正規表現が速いのでBun上だと顕著です。

Honoのv3が出ました

JavaScriptCore は V8 より速い?

大きく見ると、内部的な最適化の違いがあるようです。
ただ、例えにあるように変速ギアが多いほど速いのかは、ちょっと私には分からなかったです。
(この辺りは機会があれば調べてみたい)

V8は3段変速ギアと表現できます。

  • Ignition(1速)… 単にbytecodeを生成する
  • Sparkplug(2速)… bytecodeから変数解決/脱糖衣構文化などを実施する
  • Turbofan(3速)… 統計情報をもとに型レベルでの最適化を実施する

一方でJSCは4段変速ギアと表現できます。

  • LLInt(1速)… 単にbytecodeを生成する
  • Baseline JIT(2速… bytecodeから簡単なJIT生成コードを作る
  • DFG JIT(3速)… データフローに基づく最適化を実施し、主に戻り値の型チェックなどを行って最適化する
  • FTL JIT(4速)… SSAによる最適化(型レベルでの最適化)を実施する(V8のTurbofanと同様)

Bunファーストインプレッション - JavaScriptランタイム界に”赤壁の戦い”を! ~TechFeed Experts Night#8講演より | gihyo.jp

API互換性 (WinterCG)

WinterCG (Web-interoperable Runtimes Community Group) は、非Webブラウザを中心とした JavaScript Runtime における相互運用性の改善を目指したコミュニティグループです。
これにより、非Webブラウザ環境でのJavaScriptの実行環境の互換性が向上することが期待されます。

ただ最近は Node.js API への互換性が高まっているので、Node.js API をベースに互換性を高めていくように鳴なかもしれません。

それぞれの使い所

あくまで個人的な意見ですので、参考程度にご覧ください。
一言で表すと、以下のような感じになるかと思います。

  • Node.js: 実績重視。過去の資産を問題なく使いたい。
  • Deno: セキュリティ・開発体験を重視。Deno Deploy を使いたい。
  • Bun: パフォーマンス重視。

Node.js

まだまだ主流の環境であり実績も豊富なので、Production 環境で使う第一選択肢ではないかと思います。

また、Deno, Bun も Node.js との互換性を高めているとはいえ、ライブラリが使えないケースが出てきた時など互換性に問題が生じた場合はやはり Node.js を使うことになるのではないかと思います。

現時点では最も普及し実績もある JavaScript Runtime であり、Node.js をベースに考え、使える場合は Deno や Bun を使うという感じになるのではないかと思います。

Deno

Deno はセキュリティを最初から導入したり、ESModules のみ対応するなど、理想の形から作られているランタイムだな、と感じます。(筆者個人としては好き)

Deno しかできない点としては、パーミッションを使った実行時の高いセキュリティが必要な場合は第一選択肢になるのではないかと思います。また、開発体験を重視しており、例えば開発に使われるツール(テスト、フォーマッターなど)が含まれているので、特に新規に開発する場合には有力な選択肢の一つとなるかもしれません。

またランタイムそのものではないですが、Deno Deploy が使えるというのも大きな魅力だと思います。GitHub リポジトリと連携した時のリリース体験はとても良いです。npm サポートや Deno KV を使える(ただし執筆時点でベータ版であることに注意)なども含め、良い選択肢になるのではないかと思います。

デメリットとしては、最近は Node.js や npm との互換性が高まってきてはいるものの、互換性に関する問題が発生する可能性はあると思います。Bun と比較してもこの領域で問題が発生する可能性は高いのではないかと思います。

Bun

Bun は現状の JavaScript (+TypeScript) の環境をまるっとサポートして Node.js からの置き換えを容易にしつつ、特に実行速度を重視しているランタイムだと感じています。

これまでの Node.js プロジェクトを比較的容易に置き換えられるのではないかと思います。ただ、まだ 1.0 が出たばかりであり、Node.js と互換性も完璧ではないのでも、特に Production で使うには不安かなと思います。

個人的には、まず開発環境や個人開発などで使ってみるというのは良いのではないかと思いました。bun install のスピード感はとても気持ち良いです。

おわりに

主要な JavaScript Runtime が3つもあると、それぞれが影響を与えて改善されている感じがしますね。JavaScript を使う開発者としては、とても嬉しいです。

Node.js を追う Deno, Bun も Node.js API や npm サポートなど、できることが似通ってきている印象を受けます。今後は、実行スピードや他のエコシステムとの連携なども競争のポイントとなってくるのではないかな、と思います。

王者 Node.js に対し、Deno はセキュリティや開発体験、Bun は互換性や実行速度を武器に追いかける形がしばらく続くのではないかと思います。1年後とかにどうなっているのか楽しみですね。

おまけ

Workerd

Cloudflare Workers で使用されている workerd という JavaScript Runtime が GitHub で公開されています
JavaScript エンジンは V8 を使用しているようです。

"JavaScript" は Oracle の商標

Sun を買収した Oracle が "JavaScript" の商標を持っているらしいです。
たぶん手放さないと思うけど、手放したら個人的には Oracle にちょっと好感持てそう。

GitHubで編集を提案

Discussion