この記事は、ニフティグループ Advent Calendar 2024 11日目の記事です。
こんにちは!
今回は、JavaScriptとTypeScriptの三大ランタイム環境であるDeno、Bun、Node.jsについて比較していこうと思います。
概要
- Node.js: 2009年に登場した最も成熟したJavaScriptランタイム
- Deno: 2018年にNode.jsの創始者によって開発された、より安全でモダンなランタイム
- Bun: 2022年に登場した高速で全機能を備えたJavaScript/TypeScriptランタイム
主な特徴
Node.js
- 広大なエコシステムと豊富なnpmパッケージ
- 長年の実績と安定性
- 非同期I/Oに最適化
Deno
- セキュリティ重視(デフォルトで安全)
- TypeScriptのネイティブサポート
- URLベースのモジュールインポート
Bun
- 高速な起動とランタイム実行
- Node.jsとの高い互換性
- 内蔵のパッケージマネージャとバンドラー
比較
- パフォーマンス比較簡単なベンチマークとして、各ランタイムで1から1000万までの数を数える処理の実行時間を比較してみましょう。
Node.js
- Homebrewや公式サイト(https://nodejs.org/en)などでインストールする
- benchmark.jsというテストファイルを作成する。
1234567891011121314console.log('Starting benchmark...');const start = process.hrtime.bigint();let count = 0;for (let i = 0; i < 10000000; i++) {count++;}const end = process.hrtime.bigint();const duration = Number(end - start) / 1_000_000; // ナノ秒からミリ秒に変換console.log(`Counting completed in ${duration.toFixed(3)} ms`);console.log(`Final count: ${count}`);
- 実行する
1node benchmark.js
- 実行結果
12345678910111213141516171回目$ node benchmark.jsStarting benchmark...Counting completed in 7.381 msFinal count: 100000002回目$ node benchmark.jsStarting benchmark...Counting completed in 8.529 msFinal count: 100000003回目$ node benchmark.jsStarting benchmark...Counting completed in 8.092 msFinal count: 10000000
Deno
- Homebrewなどでインストールする
- benchmark.jsというテストファイルを作成する。
1234567891011121314console.log('Starting benchmark...');const start = performance.now();let count = 0;for (let i = 0; i < 10000000; i++) {count++;}const end = performance.now();const duration = end - start;console.log(`Counting completed in ${duration.toFixed(3)} ms`);console.log(`Final count: ${count}`);
- 実行する
1deno run benchmark.js
- 実行結果
12345678910111213141516171回目$ deno run benchmark.jsStarting benchmark...Counting completed in 10.852 msFinal count: 100000002回目$ deno run benchmark.jsStarting benchmark...Counting completed in 10.905 msFinal count: 100000003回目$ deno run benchmark.jsStarting benchmark...Counting completed in 9.019 msFinal count: 10000000
Bun
- Homebrewなどでインストールする
- benchmark.jsというテストファイルを作成する。
1234567891011121314console.log('Starting benchmark...');const start = process.hrtime.bigint();let count = 0;for (let i = 0; i < 10000000; i++) {count++;}const end = process.hrtime.bigint();const duration = Number(end - start) / 1_000_000; // ナノ秒からミリ秒に変換console.log(`Counting completed in ${duration.toFixed(3)} ms`);console.log(`Final count: ${count}`);
- 実行する
1bun benchmark.js
- 実行結果
12345678910111213141516171回目$ bun benchmark.jsStarting benchmark...Counting completed in 7.849 msFinal count: 100000002回目$ bun benchmark.jsStarting benchmark...Counting completed in 9.821 msFinal count: 100000003回目$ bun benchmark.jsStarting benchmark...Counting completed in 14.227 msFinal count: 10000000
結果
すべてほぼ同等のパフォーマンスを示しています。
HTTPサーバー作成
今度は各ランタイムで簡単なHTTPサーバーを作成してみます。
Node.js
- server.jsというファイルを作成します。
12345678910const http = require('http');const server = http.createServer((req, res) => {res.writeHead(200, { 'Content-Type': 'text/plain' });res.end('Hello Worldn');});server.listen(3000, () => {console.log('Server running at http://localhost:3000/');});
- 実行
12$ node server.jsServer running at http://localhost:3000/
- TypeScriptファイルを試してみる
- server.tsを作成する
12345678910import * as http from 'http';const server: http.Server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => {res.writeHead(200, { 'Content-Type': 'text/plain' });res.end('Hello Worldn');});server.listen(3000, () => {console.log('Server running at http://localhost:3000/');});
- 実行
- 実行するとエラー
1234567891011121314151617$ node server.ts(node:38672) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.(Use `node --trace-warnings ...` to show where the warning was created)/Users/server.ts:1import * as http from 'http';^^^^^^SyntaxError: Cannot use import statement outside a moduleat wrapSafe (node:internal/modules/cjs/loader:1350:18)at Module._compile (node:internal/modules/cjs/loader:1379:20)at Module._extensions..js (node:internal/modules/cjs/loader:1518:10)at Module.load (node:internal/modules/cjs/loader:1249:32)at Module._load (node:internal/modules/cjs/loader:1065:12)at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:158:12)at node:internal/main/run_main_module:30:49Node.js v22.2.0
- 実行するとエラー
- TypeScriptをネイティブサポートしていないことが分かります。
- server.tsを作成する
Deno
- server.tsというファイルを作成します。
- DenoはTypeScriptをネイティブサポートしています。
12345678import { serve } from "https://deno.land/std@0.181.0/http/server.ts";const handler = (req: Request): Response => {return new Response("Hello Worldn", { status: 200 });};console.log("Server running at http://localhost:3000/");await serve(handler, { port: 3000 }); - 実行
Denoはセキュリティを重視しているため、ファイルアクセスやネットワークアクセスにはパーミッションが必要です。
例えば、ファイル読み書きの場合は以下のようにします。
1deno run --allow-read --allow-write file_io.ts12345678910111213$ deno run --allow-net server.tsServer running at http://localhost:3000/Listening on http://localhost:3000/ネットワークアクセス許可(--allow-net)を与えないで実行すると、許可してよいかと聞かれます。$ deno run server.tsServer running at http://localhost:3000/┏ ⚠️ Deno requests net access to "0.0.0.0:3000".┠─ Requested by `Deno.listen()` API.┠─ To see a stack trace for this prompt, set the DENO_TRACE_PERMISSIONS environmental variable.┠─ Learn more at: https://docs.deno.com/go/--allow-net┠─ Run again with --allow-net to bypass this prompt.┗ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >
Bun
- server.tsというファイルを作成します。
- BunはTypeScriptをネイティブサポートしています。
12345678const server = Bun.serve({port: 3000,fetch(req) {return new Response("Hello Worldn");},});console.log(`Listening on http://localhost:${server.port}`); - 実行
12$ bun run server.tsListening on http://localhost:3000
DenoとBunの比較
1. モジュールのインポート:
- Deno: 外部モジュールをURLから直接インポートします。
1import { serve } from "https://deno.land/std@0.181.0/http/server.ts";
- Bun: 組み込みのグローバル
Bun
オブジェクトを使用し、追加のインポートは不要です。
2. サーバーの設定:
- Deno:
serve
関数を使用し、ハンドラー関数と設定オブジェクトを渡します。1await serve(handler, { port: 3000 }); - Bun:
Bun.serve
メソッドを使用し、設定オブジェクトを渡します。1const server = Bun.serve({ ... });
3. リクエストハンドラー:
- Deno: 独立した関数として定義します。
1const handler = (req: Request): Response => { ... };
- Bun: 設定オブジェクト内の
fetch
メソッドとして定義します。1fetch(req) { ... }
比較ポイント
上記の比較結果では、各ランタイムの HTTP サーバー実装の違いがありました。
以下に、主要な比較ポイントをまとめました。
1. TypeScript サポート:
- Node.js: 追加のツール (ts-node) とタイプ定義 (@types/node) が必要。
- Deno: ネイティブサポート。
- Bun: ネイティブサポート。
2. セキュリティモデル:
- Node.js: デフォルトですべてのシステムリソースにアクセス可能。
- Deno: 明示的な権限付与が必要(
--allow-net
フラグ)。 - Bun: Node.js 互換のモデルを採用(デフォルトでアクセス可能)。
3. API設計:
- Node.js: 標準的な http モジュールを使用。
- Deno: モダンな Web 標準に基づいた API。
- Bun: シンプルなオブジェクトベースの API。
4. モジュールシステム:
- Node.js: ES Modules (import 構文) を使用。
- Deno: ES Modules と URL ベースのインポートを使用。
- Bun: 組み込みの Bun オブジェクトを使用。
5. 実行の簡単さ:
- Node.js: TypeScript を直接実行するには追加のセットアップが必要。
- Deno: 追加のセットアップなしで TypeScript を直接実行可能。
- Bun: 追加のセットアップなしで TypeScript を直接実行可能。
結論
結論として、Node.js、Deno、Bunの3つのランタイムは、それぞれ独自の強みを持ち、JavaScriptエコシステムの多様化と進化に大きく貢献しています。
また、これらのランタイムは急速に進化を続けているため、定期的に最新の動向をチェックし、必要に応じて選択を見直すことをお勧めします。
本ブログが、読者の皆様のプロジェクト選択や技術的な意思決定の一助となれば幸いです。
より詳細な情報や具体的な使用例については、各ランタイムの公式ドキュメントを参照することをお勧めします。
明日は、Tsan0409さんの記事です。お楽しみに。