本セッションの登壇者
セッション動画
「Node.jsの2022年と未来」というタイトルで話します。よろしくお願いします。サイボウズでフロントエンドエンジニアをやっている shisama です。
今日はNode.jsの18と19の主な変更点を紹介したいと思います。その後は、現在実装中の機能から、いくつかおもしろそうなものをピックアップし、最後に時間があれば(Node.jsの)今後の10年について紹介したいと思います。
Node.js v18の主な変更点
今年は4月にNode.js v18がリリースされました。そして、11月にLTSとしてv18.12.0がリリースされました。また、このNode.js v18は3年後の2025年4月30日にメンテナンスが終了する予定です。
Node.js v18で何が入ったかというと、まずはfetch()関数がフラグがなくても実行可能になりました。これはブラウザのfetch()関数とインタフェースが同じfetch()関数というものです。Node.js v15かv16くらいで実装されていたのですが、そのときはまだ --experimental-fetch
というフラグが必要でした。しかし、v18からはフラグがなくても使えるようになっています。
fetch()関数自体は、フロントエンドのコードを書いたことがある人は一度は見たことがあると思いますが、HTTPのリクエストを送信する関数です。ただしNode.jsのfetch()はまだExperimentalな機能なので、使用には注意してください。また、ブラウザと同じよう、グローバル関数として使用することができます。なのでrequire
とか import
などを使ってモジュールを読み込む必要はありません。
次に、テストランナーの追加について話したいと思います。これはJestやVitestのようなテストランナーがNode.js本体にも追加されたという変更です。
ここに書かれているコードのように、node:test
というところからテスト関数をimportし、そのテスト関数の第1引数にテストの説明(description)を書き、第2引数にコールバック関数を書き、そのコールバック関数の中にテストコードを書く、および何かを検証するコードを書くという使い方になります。このテストコードをどうやって実行するかというと、node –test
という --test
オプションを付けたコマンドで実行できます。また、このテスト関数にはオプションを付与することができ、skip
などを指定することができます。
次に、V8(JSエンジン)がバージョンアップしました。V8というのはNode.jsの中で使われているJavaScriptエンジンで、Google ChromeやMicrosoft Edgeなどでも使われています。Node.jsではメジャーバージョンが上がるタイミングでV8が更新されますが、Node.js v18ではV8が10.1にバージョンアップされました。これで何ができるようになったかというと、Arrayの findLast
や Intl.supportedValuesOf()
が使えるようになりました。
次にHTTP requestTimeout()のデフォルト値が変更されました。server.requestTimeout
のタイムアウト時間のデフォルト値が0から300000ミリ秒(5分)になりました。もともと、サーバを起動するときに requestTimeout
というものを指定できたのですが、たとえば600000ミリ秒とか300000ミリ秒とか指定することができます。
しかし、これはオプショナルな引数なので省略することもできます。この省略した場合の値が今までは0が指定されていたのですが、Node.js18からは300000ミリ秒になります。なぜ0がダメだったのかというと、0というのはタイムアウトなしという意味になるので、たとえば処理が長いリクエストがあるとその完了を待つことになるんですよね。なので、めちゃくちゃ長い処理がきたときにその待ち時間が発生してしまうので、いいタイミングでタイムアウトにして次のリクエストを捌けるようにすることが、多くのユーザに使ってもらうためには必要ということで「5分」がデフォルト値になりました。0を明示的に指定することも可能ですが、それは推奨されていません。
次に、Web Streams APIがフラグなしで使えるようになりました。これも、Node.js v17以前から実装されていたのですが、フラグが必要でした。それがv18からはフラグがなくても使えるようになります。
これは、ブラウザのStream APIと同じインターフェースや仕様に沿ったStreamのAPIです。Node.jsからは古くからStreamというAPIがありますが、これとは互換性がありません。また、このWeb Streams APIはまだExperimentalな機能です。
次に、--watch
フラグです。これはWebpackや、Jestでおなじみのwatchモードというものが、Node.js本体にも導入されました。これは何かというと、実行中のプログラムの依存関係にあるモジュールに変更があったら自動的にプロセスを再起動する機能になります。
たとえば、node index.js –watch
のように --watch
フラグを付けて、index.jsをエントリポイントとして、Nodeのプロセスを起動したとします。そのときに、ここに書かれているようなModule A/ Module B/Module Cという依存関係があった場合、この4つのファイルのどれかに変更があったときにそのNode.jsのプロセスが自動的に再起動されるという機能です。
これまではNode.jsを起動して、開発者が何かコードを修正したら、Node.jsのプロセスを止めて再起動するという作業を手動で行わなければならなかったのですが、watchモードがあることによって、再起動する手間が省けて開発効率が上がることになります。これはv18.11.0から使えます。
Node.js v19の変更点
次に、Node.js v19について話をしていきたいと思います。
HTTP(S) Keep Aliveがデフォルトで有効になりました。Keep Aliveというのは、一定時間HTTPの接続を維持するもので、それによってハンドシェイクの回数を減らしてパフォーマンスを良くできるメリットがあります。
このKeep Aliveはこれまでも明示的に指定することが可能でした。このコードのように、{keepAlive: true}
と指定してHTTPリクエストを投げることができますが、省略することも可能です。今までは省略するとKeepAliveが有効にならなかったのですが、今回からはデフォルトで有効になるようになっています。
次に、V8のバージョンアップです。Node.js v19でもV8が10.7にアップデートされました。これによって Intl.Numberformat v3
が使えるようになります。どういったものがあるかというと、範囲をフォーマットする formatRange()
という関数などが使えるようになっています。
次に--experimental-specifier-resolution
フラグが削除されました。ECMAScript Module(ESM)のコードを書くときに、importのパスは拡張子を書かなくてはいけないのですが、そのパスの中の.jsとかを省略したいとき、これまでは--experimental-specifier-resolution=node
というフラグを付けて実行することでNode.jsが自動的に拡張子を補完してくれていました。しかし、ESMは仕様上、拡張子の省略が許されてないので、この仕様に沿う形でこのフラグが削除されました。
代替手段としては、Custom Loaderというものが用意されています。拡張子を補完するスクリプトを書いて、Node.jsを実行するときに指定することで、拡張子を補完することができるというものです。
Custom Loaderについては、node.js/loaders-testというリポジトリにテストコードがあり、そこにTypeScriptのLoaderや、CoffeeScriptのLoader、拡張子を補完するLoaderなどのテストコードがあるので、そちらをサンプルとして参考にして実装するといいと思います。
次に、Web Crypto APIがstableになりました。これはブラウザ(Web)のCrypto APIとインターフェースの互換性があるもので、Node.jsの古いCrypto APIとは互換性はありません。何ができるかというと、暗号化や復号ができるAPIになっています。
次に、ShadowRealmです。ShadowRealmとはJavaScriptを実行するためのグローバル環境(Realm)を新しく生成するためのAPIです。ここに掲載されているコードを見ていただくと使い方はなんとなくわかるかと思いますが、ShadowRealmのインスタンスを生成してこの realm.evaluate()
という関数を呼び出してその関数の引数に書いた文字列のJavaScriptをRealmの環境内で実行できるというものになります。
これのメリットとして、トップレベルのJavaScriptのコンテキストとは別の実行環境を作ることができるので、お互いのグローバル環境を汚染しあわないという点があります。このコードでいうと、トップレベルの globalThis.foo
に foo
という文字列を入れて、Realmの中では bar
という文字列を入れてても、トップレベルの foo
は文字列 foo
が入ったまま、Realmの中のグローバル変数 foo
は文字列 bar
が入っているというように、お互い干渉しあわないというメリットがあります。このおかげで、サンドボックス環境を作ることができるようになります。
ShadowRealm自体は現在ECMAScriptの仕様のproposalのstage3という扱いになっています。Node.jsには古くからvm APIというものがあって同じようなことができますが、このShadowRealmを使うことでブラウザと互換性をもてることがポイントになります。しかし、まだExperimentalな機能です。
ShadowRealmについては、petamorikenさんのzennの記事が詳しいのでそちらを読んでいただけるといいかなと思います。
ここまで話したNode.js v18とv19の変更点以外にも紹介できなかったものがあるので、興味があればブログも読んでみてください。
Node.js in the Future
ここからは、Node.jsの未来について紹介していきます。まずは現在実装中の機能からおもしろそうなものをピックアップして紹介したいと思います。
ひとつめですが、Node.js v18で追加されたテストランナーにモックの機能が追加されるというもので、プルリクエストはすでにマージされています。なので、近い将来使えるようになるかと思います。
使い方としては、このコードの通りで、node/test
からimportし、そこからモック関数を作り、そのモック関数が呼ばれた回数を検証するといったことができます。
ほかにも、検証している関数の中で使っているモジュールなどをモック化したり、そのモックしたものを元に戻すといったAPIもあります。
次に、node:http/staticです。これは静的ファイル用のHTTPサーバを簡単に起動することができるという機能です。このプルリクエストはまだマージされていません。
何ができるかというと、node -r node:http/static /path/to/static –port 8080
といったコマンドを実行すると、この /path/to/static
ディレクトリの中に入っているHTMLやCSS、JS、画像といった静的なファイルを配信するサーバを起動することができます。なので、サーバのコードを自分で書かなくてもよくなります。すでにnpm packageとしてhttp-serverがあったり、Pythonにもhttp.serverというものがあったりしますが、その代替として使えるかと思います。
次にQUICです。QUICのプルリクエストもまだマージされていません。これはどういうものかというと、トランスポートプロトコル、つまり通信プロトコルのひとつです。特徴としては、データ再送の効率化や通信の継続性、内部的にTLSを使用しているので暗号化前提という特徴があります。
HTTP/3と呼ばれるプロトコルは、このQUICを土台に動きます。なので次の図の通り、HTTP/2はTLS、TCPがありますが、HTTP/3は、UDPの上にQUICがあり、そのQUICがTLSを内包していて、その上にHTTPがあるという形になります。
QUICやHTTP/3に関しては後藤ゆきさんが書かれたgihyo.jpの記事がすごくわかりやすくて詳しいので、そちらを読んでみてください。
次に、Single Executable Applicationです。これもまだプルリクエストがマージされていません。これは何かというと、複数のJSファイルをひとつにまとめて、さらにWIndowsなどで実行できる実行ファイルに変換するというものです。
Webpackなどで複数のファイルをひとつにまとめるということはできますが、まとめたファイルを実行するにはNode.jsが必要だったり、ほかにもブラウザ上で動かさなければならないという制約があるので、JavaScriptのランタイムが必要です。Node.jsやJavaScriptのランタイムがインストールされていないユーザー向けにアプリケーションを配布したいときに、Node.jsでWindowsやMac向けに実行ファイルを作り、その実行ファイルを配布することで、ユーザーはNode.jsをインストールしなくてもそのアプリケーションを使うことができるという機能です。代替手段としては、vercelがpkgというパッケージを出していて、それを使うと同じようなことができます。
次にパーミッションモデルです。これもまだプルリクエストが出ていますが、マージされていないどころかまだドラフトです。
これは何かというと、あとのセッションの日野澤さんがお話しされるDenoのように、実行時にパーミッションを付与することで、ファイルアクセスやプロセスの軌道などを制御し、セキュアな実行を可能にするというものです。どういうことかというと、悪意のあるコードをNode.jsを使って実行してしまったときに、その悪意のあるコードがそのマシンの中のファイルにアクセスして秘密情報を抜き取ってサーバに送るという攻撃ができてしまう可能性があります。しかし、実行するスクリプトはそもそもファイルアクセスが必要ないと思って実行している場合、そのファイルアクセスを禁止して実行することで悪意あるスクリプトが混在していたとしてもファイルにはアクセスできないようにNode.jsが制御することで防ぐ機能です。
パーミッションを付与する方法については、Denoのようにそもそもファイルアクセスを禁止しておいて、許可制のフラグにするのか、禁止形式にするのかどうかも決まっていません。
Node.jsの次の10年に向けて
今後10年についても簡単に紹介したいと思います。
Node.jsの今後10年どうするかという議論が始まっていて、リポジトリがnodejs/next-10というところにあります。その中に優先度の高い機能の一覧と関連リンクが掲載されている文章や、Node.jsのリポジトリ内に優先度の高い機能の概要が掲載されていたりします。
これが、nodejs/nodeにあるTecnical Prioritiesという文章です。いくつか項目があり、それぞれ簡単な説明があるので興味があれば読んでみてください。
その中に書いてあるものとしてはこのようなものがあります。今回紹介したQUICやパーミッションモデル、Single Executable Applicationsなどもこの中に含まれています。
その中でもおもしろそうなものをピックアップして紹介します。
まずはSuitable types for end-usersです。これは、メンテナブルで信頼度の高い方をユーザーに提供したいというもので、どうやってやるのかを現在議論中です。
ほかにも、ECMAScriptの最新の機能をサポートしたいということも議論されています。
まとめ
駆け足になってしまいましたが、最後にまとめです。
- Node.js v18とv19にはWeb互換な機能がいくつかフラグなしで使えるようになったり、stableになったりと、Web互換が強くなりました。
- テストランナーや--watchモードなど、開発体験を向上させるための機能も追加されました。
- また、Node.jsの次の10年に向けて議論が開始されています。
- そして、優先度が高い機能から実装や議論が始まっています。
それから、今月26日にJSConf JPがあるのですが、その中でもNode.jsについてのトークがあるので、興味があったら参加してみてください(注: こちらのイベントは終了しました)。
ご清聴、ありがとうございました。