2024年6月19日
執筆
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)に所属するテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。著書に『Bootstrap 5 フロントエンド開発の教科書』、『作って学べるHTML+JavaScriptの基本』など。
監修
静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。
主な著書に「独習」シリーズ、「これからはじめるReact実践入門」、「改訂3版 JavaScript本格入門」他、著書多数。
「新発見!フロントエンド技術の今」の連載。第8回のテーマは「JavaScriptランタイム」です。
JavaScriptランタイムとは、JavaScriptプログラムの実行環境全般をいいます。JavaScriptが登場してからしばらくの間は、ブラウザが主な実行環境でした。WSH(Windows Scripting Host)やJava(Rhino)の存在はあったものの、それらの動作環境や用途は限られており、JavaScriptといえばブラウザ、であったわけです。これが、2009年にNode.jsが登場したことで一変します。
最初に用語の整理をしておきます。まず、JavaScriptコードの解釈と実行を担うのが「JavaScript実行エンジン」。ブラウザにも以下のようなJavaScript実行エンジンが搭載されています。
そして、これらの実行エンジンに、モジュールシステムやAPIなどを加えたJavaScriptの実行基盤が「JavaScriptランタイム」です。広い意味ではブラウザもJavaScriptランタイムと言えますが、本稿で主に扱うのは、以下のようなJavaScriptランタイムです。
たとえばNode.jsであれば、JavaScript実行エンジンにV8を採用し、独自にモジュールシステムとAPIを装備しています。ブラウザと異なるのは、構築できるアプリがWebアプリに限らず、モバイルアプリ、デスクトップアプリなどと、非常に汎用性が高いことです。
vol.7でも紹介した「State of JavaScript」の、最新の2022年版の結果を紹介します(2023年版は集計中)。「Other Tools」(その他のツール)の「JavaScript Runtimes」は、「Which engines/runtimes/execution environments do you regularly use?」(いつもどんなエンジン/ランタイム/実行環境を使っている?)という複数選択式の質問に対する回答です。
Browser(61.8%)とService Workers(13.5%*)はブラウザ実行環境で、Hermes(2.3%)とChakraCore(0.3%)はJavaScript実行エンジンです。ポイントが最大のNode.js(70%)で、ブラウザを超える存在感を示しています。Deno(8.5%)、Bun(3.2%)は新手とあって、まだまだ利用の割合は少ないものの、ここ数年でめきめきと頭角を現しています。
*ブラウザで動作するバックグラウンドの動作環境のことを言います。
さて、Node.jsの最初のバージョン0.1は、2009年に登場していますが、そもそも、ブラウザで動くのが当たり前とされていたJavaScriptをブラウザ外で動かそうというモチベーションはどこにあったのでしょうか?
それは、2007年ごろ話題に上ることが多かったC10K問題への回答と言われています。C10K問題とは、クライアントからの接続が1万を超えたあたりから、急速にレスポンスが悪化するという事象で、当時のWebサーバの構造上の問題とされています。これに対して、1スレッドでイベントループを回してクライアントからのリクエストに応答する技術として、JavaScriptが注目されました。そこで、JavaScriptをサーバサイドなどのブラウザ外で本格的に使えるようにしたのが、Node.jsであったわけです。
よく利用されるJavaScriptランタイムは、長い間、Node.jsのみといった状況でしたが、その後、DenoやBunを代表とするいくつものランタイムがリリースされ、機能と性能を競っています。これら3つのランタイムについて個別に言及するにあたり、採用しているJavaScript実行エンジンやプログラミング言語などを概観できるように、あらかじめ表にまとめておきます。
Node.jsは、現時点でデファクトスタンダードなJavaScriptランタイムです。JavaScriptランタイム=Node.jsという向きも多いでしょう。2009年にRyan Dahl氏によってリリースされ、OpenJS Foundationにより開発が進められています。本稿作成時点の最新版は20.13です。
Node.jsは、最古参のJavaScriptランタイムであり、現在のJavaScriptエコシステムの基礎を築いています。後続のランタイムにおいても、コア技術となるしくみを備えているので、個々について押さえておきます。
リリース直後のNode.jsは、モジュールシステムとしてCommonJSを採用しました。CommonJSは、ブラウザ外でのJavaScript実行環境の標準を定めようとするプロジェクトで、モジュールシステムは、そのうちのひとつです。しばらくして、ECMAScript 2015(ES6)の策定でモジュール規格であるES Modulesの普及に従い、Node.jsでも対応が進みます。それでもCommonJSモジュールを前提とした資産は膨大なため、現時点では、CommonJSモジュールとES Modulesは棲み分ける形になっています。
Node.jsでは、さまざまなライブラリが利用できるのも特長のひとつですが、そのライブラリを手作業で準備していくのは(依存関係のあるライブラリも意識しながら!)、厄介なことです。
そこで利用するライブラリ(パッケージ)の依存関係を解決し、インストールの段取りを賄ってくれるのがパッケージマネージャです。Node.jsのパッケージマネージャで有名なのは、「npm」と「yarn」です。
npmは「Node Package Manager」の略で、2010年10月にリリースされた、Node.jsの公式のパッケージマネージャです。特に理由のない限りはnpmを使っている開発者も多いでしょう。
yarnは「Yet Another Resource Negotiator」の略で、2016年10月にリリースされた、比較的新しいパッケージマネージャです。yarnリリース当時のnpmには、インストール時間やセキュリティ上の問題点があり、yarnはこれを速度面で解決する形で開発、リリースされました。また、異なる環境でのインストールパッケージの再現も容易になりました。
とはいえ、npmの方もインストールパッケージの再現、インストール速度の改善などでyarnと遜色ない性能になり、現在は両者の差はほとんどないという状況になっています。
OSの機能の利用をはじめとした、多彩なAPIが利用可能です。ファイルシステム、ネットワーク、コンソール、プロセスなどのカテゴリが用意されています。ただしNode.jsの特性として、これらのAPIの利用には特に制限がないため、セキュリティ上の弱点とされています。
次は、Denoです(DenoはNodeのアナグラム)。Node.js開発者のRyan Dahl氏自らが開発したJavaScriptランタイムです。2018年にバージョン0.1がリリースされ、2020年にバージョン1.0に到達した後、本稿作成時点のバージョンは1.43です。Deno Land社によって開発が進んでいます。Denoという名称から想像がつくように、Denoのアイコンはかわいらしい恐竜のイラストです。
Denoは、Node.jsとの互換性を大きく維持しながら高速性とセキュリティ向上を追求し、TypeScriptトランスパイラなど独自の価値を提供しているランタイムです。
Denoは、Ryan Dahl氏の講演「10 Things I Regret About Node.js – Ryan Dahl – JSConf EU」で発表されました。
講演の内容は、Node.jsには10個の設計ミスがあるというものです。その一つとしてモジュールシステムの失敗があるとされていますが、CommonJSにおけるモジュール指定がES Modulesの標準から外れたことが大きいようです。それでも、npmエコシステムには多くの資産があるので、今さら捨てられないという事情があり、そこで新しいランタイムを開発するというモチベーションになったようです。
Denoにおけるモジュールの指定は、ES Modulesと同じexportとimport~from形式です。モジュールは、実行時に自動的にダウンロードされるので、パッケージマネージャ(と、その動作のためのpackage.jsonやnode_modulesフォルダ)が不要になります。ダウンロードされたモジュールはキャッシュされるので、2回目以降の呼び出しは高速になります。
ただし、2022年リリースのバージョン1.28で、Node.jsの資産が膨大であることからnpmパッケージ(CommonJS)を利用できるようになりました。以降は、CommonJSとES Modulesの双方を利用できるようになっています。
Denoでは、モジュールを独立した環境で実行できるサンドボックスモードが採用されています。またNode.jsには、環境変数、ファイルシステムやネットワークアクセスへの制限がないため、これがセキュリティの弱点となっていました。Denoは、これらは原則、利用禁止となっており、利用したい場合には明示的に–allow-net(ネットワークアクセスの許可)、–allow-write(ファイル書き込み権限の許可)などのオプションを、deno runコマンドなどに指定する必要があります。
Denoは、TypeScriptのトランスパイラを備えています。Node.jsでは別途トランスパイラをインストールして、必要な設定を行うなどの準備が必要でしたが、これが不要になったのもメリットです。
最後はBunです。Bunは、Node.jsやDenoなどの既存ランタイムの置き換えを目標に開発がスタートしました。Oven社によって開発が進んでいます。2023年9月にバージョン1.0がリリースされた、最も新しいJavaScriptランタイムです。本稿作成時点の最新版はバージョン1.1です。ちなみにBunの名称は「肉まん」からきているらしく、アイコンもかわいらしい肉まんのイラストです。
Bunを表現すると、Node.jsとの互換性を大きく維持しながら高速性を追求し、TypeScriptトランスパイラやバンドラなど独自の機能を持っているランタイムです。
Bunの最大の特徴は速度でしょう。公式サイトでベンチマークの比較結果が公表されていることから分かるように、リリース元がそれを大きく打ち出しています。指標の一つである「Bun.serve()」(Server-side rendering React / HTTP requests per second (Linux x64)=1秒あたりのReactのSSR処理)ではNode.jsに対して4倍の性能を出しています。
その他の指標である「WebSocket」(WebSocket chat server / Messages sent per second (Linux x64, 32 clients))、「bun:sqlite」(Load a huge table / Average queries per second)でもほぼ同様の傾向です。
Node.jsとDenoはV8エンジンが基本となっていて、BunではJavaScriptCoreが基本となっています。実行エンジンの性能差と、プログラミング言語Zigによる実装の性能差で、このような差が出ていると思われますが、ベンチマークはあくまでも局地的なもので、実際にはそこまでの差にはならないという見方もあります。
APIやパッケージ管理システムなどNode.jsとの互換性が高いのも大きな特徴です。公式サイトに互換性の情報が掲載されているのですが、多くのAPIが緑色(フル実装)となっており、黄色(部分実装)と赤色(未実装)を大きく上回っています。利用しているAPIにもよりますが、Node.jsで稼働させていたアプリをそのまま実行できる下地が整っているといえるでしょう。同様に、パッケージ管理システムもNode.jsと互換性があるので、package.jsonが使えるほか、ES ModulesとCommonJSの両方が使えます。
Bunは、「all-in-one JavaScript runtime」を謳っており、バンドラとトランスパイラが付属します。これは、Node.jsにはない特徴です。内蔵するトランスパイラによりTypeScriptプログラム(.ts)を直接実行できるので、外部モジュールに依存する部分を少なくできます。
今回は、ブラウザ外のJavaScript実行環境である「JavaScriptランタイム」を紹介しました。本記事をきっかけに興味を持たれたならば、ぜひJavaScript実行エンジンの技術やランタイムの利用法を深めてみてください。
関連記事
人気記事