こんにちは、フロントエンドエンジニアの堀江(@nandemo_3_)です。
2023年9月にJavaScript RuntimeのBunが、バージョン1.0をリリースしました。
JavaScriptランタイムといえばNode.jsですが、DenoやBunとの違いは何か。
自分は恥ずかしながら、最近、Deno、Bunという新しいJavaScriptランタイムがあるということを知り、この際学び直しをしました。
JavaScriptランタイムってなんだ?DenoやBunってなんだ?っていう初心者向けの記事となります。
ランタイムとは
そもそもJavaScriptランタイムのランタイムとは何か。
実行すること、実行時に必要な部品。
実行環境と表現されたりします。
つまり、JavaScriptランタイムとはJavaScriptを実行する環境のことを意味し、主にサーバサイドのプログラムが実行されます。
JavaScriptランタイムの種類
それでは、JavaScriptランタイムの種類を紹介していきます。
Node.js
Node.jsは、JavaScriptランタイムといったらこれと言っても過言ではないくらい、デファクトスタンダードとなっていますよね。
Node.jsはRyan Dahlによって開発され、2009年に発表されました。
14年前(2023年現在)から存在するんだとびっくりしますが、
元々、フロントエンドのみでしか使えなかったJavaScriptが、Node.jsの登場でバックエンドでも利用可能となり、革命と言っても過言ではない出来事がこの時起きました。
圧倒的に使われているNode.js
ここで、この後紹介するDenoやBunを含むJavaScriptランタイムのGitHubのStar数(2023年10月30日時点)を比較してみます。
Node.jsとDenoに大きな差はなく、Bunも熱量の高さを感じられます。
一方、State of JavaScript 2022*1によると、
普段使用しているランタイムはNode.jsが圧倒的だということがわかります。
Node.jsの問題点
Node.jsは現在もJavaScriptランタイムの王者として君臨し、巨大なコミュニティとサポートを提供しています。
StackOverflowを見れば、大体の課題の解決策があるでしょう。
しかし、諸行無常。
Node.jsには課題があります。
Node.jsの発展とともに多くのモジュールが開発され使われるようになりましたが、それに伴ってモジュールが何重にも使われ、複雑で遅くなりました。
その要因の一つとして挙げられる課題は、Node.jsが採用しているJavaScriptモジュールシステムがあります。
登場してすぐのNode.jsは、モジュールシステムを仕組みとしてなかったため、Common JSを採用しました。
しかし、ES6で標準化されたモジュールシステムはES Modulesでした。
Common JSのコード
const { hoge } = require('./hoge')
ES Modulesのコード
import { hoge } from './hoge.js'
Node.jsで使われるモジュール類はこのどちらかを使っており、Node.jsはそのどちらも対応することにしました。
これにより、どちらのモジュールシステムを採用しているかによるトラブルが発生。
例えば、Common JS準拠のモジュールから、ES Modules準拠のモジュールを呼び出すことはできません。
その他にも、Node.jsには、セキュリティやTypeScriptサポートの課題があります。(詳しくはDenoのセクションで記述します)
もっと詳しく知りたい方は、こちらの記事にRyan Dahl氏が述べたNode.jsの後悔について詳しく記載されているので、ご覧ください。
yosuke-furukawa.hatenablog.com
こういった課題から登場したのが、DenoとBunです。
Deno
DenoはRustベースのJavaScriptランタイムです。
Node.jsと同様、Ryan Dahl氏によって、Node.jsが提供しているものの改善を目的として作成され、立ち上げられました。
2020年5月にバージョン 1.0をリリースしました。
Node.jsの開発者がDenoを作ったというのに驚きですね。
Denoが開発された背景
1. セキュリティ
Node.jsのセキュリティを改善したものがDenoと言われます。
Node.jsでは、スクリプトがデフォルトでフルアクセス権限を持っています。
一方、Denoは、sandboxモデルになっており、デフォルトではネットワークアクセスやファイルの書き込み権限がありません。
明示的に--allow-net
や--allow-write
といったオプションをつけることでアクセス権限が付与されます。
deno run --allow-read=/etc https://deno.land/std/examples/cat.ts /etc/passwd
2. モジュールシステム
Node.jsでは、上記でも述べた通りCommon JSとES Modulesの2つのモジュールシステムを採用しています。
また、npm経由でパッケージ管理されます。
一方、DenosではURLまたはファイルパスから直接importします。(リモート or ローカル)
これは、コード内にimportしたものしかimportしないということを表しています。
ちなみに、import には拡張子は必要になります。
// remote import import { add, multiply } from "https://x.nest.land/ramda@0.27.0/source/index.js"; // local import import { add, multiply } from "./arithmetic.ts";
3. TypeScriptサポート
Node.jsでTypeScriptを使う場合、型定義ファイルをインストールする必要がありますが、 Denosではネイティブサポートしているので、意識することなくTypeScriptを使うことができます。
Node.jsの場合
npm install --save-dev typescript @types/node
とはいえ、Node.jsもTypeScriptサポートが活発化している*2のも事実ではあります。
Denoはまた、「Fresh」というDenoのために作られたウェブフレームワークや、「Lume」という静的サイトジェネレーターといったエコシステムもあります。
Bun
Bunは、2023年9月に1.0をリリースしたばかりのJavaScriptランタイムです。
Bunの特徴
1. パフォーマンス
Bunの特徴は、Node.jsとDenoと比べて圧倒的にパフォーマンスが高いということです。
公式サイト*3によると、Node.jsより最大4倍早く起動すると書かれています。
これは、BunがEdgeで動くJavaScriptランタイムでシェアNo.1を取ることをゴールていることに由来します。*4
2. モジュールシステム
面白いことに、BunはCommonJSとES Modulesどちらもサポートしており、同じファイルに以下のコードのようにCommonJSとES Modulesのimportを書くことができます。
この時、package.jsonなどの設定ファイルを変更する必要はなく、どちらもimportするだけで動くようです。
import lodash from "lodash"; const _ = require("underscore");
3. TypeScriptサポート
BunもDeno同様、TypeScriptをサポートし、JSXやTSXファイルも外部ライブラリなしで利用可能*5です。
DenoでHello World
それでは、DenoでHello Worldを出力するチュートリアル*6を行っていきたいと思います。
インストール
Homebrewでインストール
$ brew install deno $ deno -V deno 1.37.2
サンプルコード
外部APIをfetchしてレスポンスをログ出力する簡単なコードです。
// index.ts async function func () { const textResponse = await fetch("https://pokeapi.co/api/v2/pokemon?limit=1"); const textData = await textResponse.text(); console.log(textData); } func();
deno run
に--allow-net
オプションをつけて実行することで、レスポンスが出力されます。
$ deno run --allow-net index.ts {"count":1292,"next":"https://pokeapi.co/api/v2/pokemon?offset=1&limit=1","previous":null,"results":[{"name":"bulbasaur","url":"https://pokeapi.co/api/v2/pokemon/1/"}]}
--allow-net
をつけずに実行すると、このようにwarningが出るので、問題があった時は非常に分かりやすいですね。
$ deno run index.ts ┌ ⚠️ Deno requests net access to "pokeapi.co". ├ Requested by `fetch()` API. ├ 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でHello World
インストール
$ brew tap oven-sh/bun # for macOS and Linux $ brew install bun $ bun --version 1.0.7
init
$ bun init bun init helps you get started with a minimal project and tries to guess sensible defaults. Press ^C anytime to quit package name (bun): test entry point (index.ts): Done! A package.json file was saved in the current directory. + index.ts + .gitignore + tsconfig.json (for editor auto-complete) + README.md To get started, run: bun run index.ts
bun init
するとNode.jsと同様なファイル群が生成されます。
bunもnode_modulesやpackage.jsonを使うようです。
$ tree . ├── README.md ├── bun.lockb ├── index.ts ├── node_modules ├── package.json └── tsconfig.json
index.tsを見てみるとconsole.log("Hello via Bun!");
だけが書かれてました。
bun run index.ts
で実行することができます。
$ bun run index.ts Hello via Bun!
サンプルコード
せっかくなのでJSXを書いてみたいと思います。
function Component(props: {message: string}) { return ( <body> <h1 style={{color: 'red'}}>{props.message}</h1> </body> ); } console.log(<Component message="Hello world!" />);
$ bun install react $ bun run index.tsx <NoName message="Hello world!" />
エラーなく出力されました。
が、NoName
になっているので何かおかしい気もしますが、ここでは一旦見なかったことにします。
Bunは、公式HPにAll in Oneと謳っていますが、JSXやNext.jsに対応しているという点で確かにそうだなと思いました。
まとめ
今回は、JavaScriptランタイムの最新動向をまとめてみました。
初心者向けの記事なので、より詳しく知りたい方は、参照サイトをご覧ください。
結局のところ、一般的には、実績のあるNode.jsが最も安全な選択肢であることに変わりはないと思います。
DenoとBunは、新しいものを作りたい方や、新しい技術の最先端に乗りたい場合に選択すべきなのかなと思いました。
特に、Bunに関しては、Next.js対応JavaScriptランタイムということで、非常興味深いなと感じました。
BunはEdgeで動くのが目的で、起動が速いのが特徴ですが、
lambdaとの相性が非常に良いですし、サーバレスフロントエンド、サーバレスNext.jsなどといった、フロントエンドのトレンドでも活用できそうだと思いました。
最後まで読んでくださりありがとうございました。
*1:JavaScriptに関心のある世界中のITエンジニア3万9472人が回答したアンケートの結果
*2:https://github.com/openjs-foundation/summit/issues/368
*3:https://bun.sh/blog/bun-v1.0
*4:https://gihyo.jp/article/2023/01/tfen005-bun
*5:下記サンプルコードにて、JSXをログ出力しましたが、reactをインストールしないとエラーになりました