別にしんどくないブログ

技術のことや読書メモを書いています

Node.js v21 の主な変更点

2023年10月17日にリリースされたNode.js v21の主な変更点を紹介します。

この記事を書いている時点ではv21.4.0が最新版ですので、v21.0.0からv21.4.0までの変更点で注目の機能をまとめています。

nodejs.org

fetchとWebStreamsが安定版へ

github.com

Webアプリケーションの開発者ならfetchを一度は見たり使ったことがあるかと思います。
Node.jsでは、fetchはv16.15.0とv17.5.0で実験的に追加されました。
v18.0.0からは--experimental-global-fetch のようなフラグを付けなくても使えるようになりましたが、まだ実験的な機能でした。
v21.0.0からは実験的な機能ではなくなりました。 88%のテストを通過しているため、まだ完全ではありませんが、安定版へと昇格しました。

また、WebStreamsも安定版になりました。

Web Streams API は Web(ブラウザ)の Stream API と同じインターフェイスや仕様に沿った新しい Stream の API です。 元々 Node.js には Stream API がありましたが、ブラウザとの互換性がない API でした。 Node.js は Web との互換性を重視するようになり、Web の API を積極的に Node.js 本体に実装しています。 そのため、従来の Node.js の Stream API は残しつつも、Web 互換な Stream API を実装しました。

詳しくはNode.js v18の変更点の記事をご覧ください。

shisama.hatenablog.com

WebSocketの実験的な実装

github.com

Webフロントエンド開発者ならWebSocketを一度は聞いたり使ったりしたことがあると思いますが、Node.jsではまだ実験的な実装となっています。

実験的な機能のため、--experimental-websocketフラグが必要です。フラグを付けて実行したときだけWebSocketオブジェクトがグローバルからアクセスできるようになっています。

$ node --experimental-websocket index.js
// --experimental-websocket フラグがないとエラーになる
const socket = new WebSocket("ws://localhost:8080");

github.com

navigator.languagenavigator.languagesはブラウザの言語設定を取得するAPIです。

developer.mozilla.org

developer.mozilla.org

Node.jsは多言語をサポートしています。

nodejs.org

navigator.languageを使えば、実行中のNode.jsのプロセスで利用されている言語を取得することができます。

console.log(navigator.language);
// en-US

navigator.languagesを使えば、実行中のNode.jsのプロセスで利用可能な言語の一覧を取得することができます。

console.log(navigator.languages);
// ["en-US", "zh-CN", "ja-JP"]

V8 11.8 による新しい JavaScript の機能

github.com

JavaScriptエンジンであるV8が11.8にアップデートしました。
それにより以下の新しいJavaScriptの機能が追加されています。

  • Array grouping
  • ArrayBuffer.prototype.transfer
  • WebAssembly extended-const expressions

ここでは、使い所が多いと思われるArray groupingについて紹介します。

TC39のproposalに記載の以下のコードを読んでいただくとわかりやすいと思います。 github.com

const array = [1, 2, 3, 4, 5];

// `Object.groupBy` は任意のキーで値をグループ化します。
// 以下は奇数(odd)と偶数(even)でグループ化しています。
Object.groupBy(array, (num, index) => {
  return num % 2 === 0 ? 'even': 'odd';
});
// =>  { odd: [1, 3, 5], even: [2, 4] }

ESM をデフォルト化するフラグ

github.com

Node.jsは歴史的経緯からECMAScript標準であるES Modules(ESM)をデフォルトで有効にしておらず、CommonJS(CJS)をデフォルトで有効にしています。 ただし、package.json"type": "module"を記述することでESMをデフォルトで有効にすることができたり、ファイルの拡張子が.mjsの場合はESMとして扱われたりします。

しかし、package.json"type": "module"を記述されておらず、拡張子が.jsの場合はESMなのかCJSユーザーの明確な指定がないため、"ambiguous" files(曖昧なファイル)とされています。

そこで、Ambiguous fileは前述のとおりCJSとして扱われていますが、v21.0.0で実験的に導入された--experimental-default-typeフラグを使うことで"ambiguous" filesをESMとして扱うことができます。以下のようにフラグをつけて実行すると、"ambiguous" fileがESMとして扱われます。

$ node --experimental-default-type=module index.js

ESMのデフォルト化については以下の記事が詳しいです。

jser.info

実行ファイルがESMなのか自動判定する実験的機能

もう1つESM周りの変更点としては、v21.1.0から--experimental-detect-moduleフラグが実験的に追加されています。
このフラグをつけると、package.jsontypeが設定されていない場合でも.jsの拡張子のファイルの中身を見て、ESMかCJSかを判別してくれます。

$ node --experimental-detect-module index.js

この判別処理に伴い実行時のパフォーマンスが低下する可能性があるため、Node.jsチームとしてはpackage.jsontypeを設定することを推奨しています。

import.meta.dirname/filename の追加

github.com

ESMを利用すると、CJSでは利用可能だった__dirname__filenameが利用できなくなります。

そこで、以下のようなコードを書く必要がありました。

// /path/to/file/index.js から実行

import url from 'url'
import path from 'path'

const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

console.log(__filename); // /path/to/file/index.js
console.log(__dirname); //path/to/file

これは煩わしいので、従来の__dirname__filenameと同じように現在のディレクトリとファイル名にアクセスできるimport.meta.dirnameimport.meta.filenameが追加されました。

上記のコードは以下のように書き換えることができます。

// /path/to/file/index.js から実行

// const __filename = url.fileURLToPath(import.meta.url);
import.meta.filename;
// => /path/to/file/index.js 

// const __dirname = path.dirname(__filename);
import.meta.dirname;
// => /path/to/file

テストランナーがglobでのテストファイル指定に対応

v18.0.0でNode.js本体にテストランナーが追加されました。 shisama.hatenablog.com

JestやVitestなどのライブラリを使わなくてもNode.js本体のテストモジュールを利用することで以下のようなテストを記述することができます。

import test from 'node:test';
import assert from 'assert/strict';

test('top level test', async (t) => {
  await t.test('subtest 1', (t) => {
    assert.strictEqual(1, 1);
  });

  await t.test('subtest 2', (t) => {
    assert.strictEqual(2, 2);
  });
});

モックやタイマーといった機能も備えており、ちょっとしたテストならカバーできると思います。詳しくはAPIの仕様をご確認ください。

nodejs.org

このテストランナーですが、v21.0.0からはglobでのテストファイル指定に対応しました。

$ node --test **/*.test.js

これまで--testなどのコマンドラインオプションは存在していましたが、以下のように個別のファイル名やディレクトリ名を指定することしかできませんでした。

node --test test1.js test2.mjs custom_test_dir/

globを使ったファイル指定ができるようになったことでテストの実行はより簡単になりました。

警告(Warning)を無効化するフラグの追加

github.com

Node.jsを実行したときに、非推奨(Deprecated)な機能や実験的(Experimental)な機能を使っていると実行時に警告が出ます。

その警告を無効化するための--disable-warningフラグが追加されました。 以下のようにフラグをつけて実行すると、特定の警告を警告が出なくなります。

$ node --disable-warning=DEP0025 index.js

上記のようにコードを指定することで特定の非推奨な機能の警告を無効化することができます。

非推奨機能のコードについては以下のリンクをご確認ください。

nodejs.org

また、非推奨な機能や実験的な機能の使用に対する警告を一括で無効化するためのオプションも用意されています。

以下は非推奨な機能の警告を無効化するためのオプションです。

$ node --disable-warning=DeprecationWarning index.js

以下は実験的な機能の警告を無効化するためのオプションです。

$ node --disable-warning=ExperimentalWarning index.js

開発者が意図的に非推奨な機能や実験的な機能を使っている場合は、警告を無効化することで警告が出なくなります。

しかし、意図していない場合は、警告を無効化することで問題が発生する可能性があります。なので、警告を無効化する場合は注意が必要です。

その他の変更点

fs.writeFile関数に'flush'オプションが追加されたり、パフォーマンスの改善されたり、他にもさまざまな変更点があります。 詳しくは以下のリンクをご確認ください。

nodejs.org

まとめ

Node.js v21はfetchWebStreamの安定リリース、WebSocketの追加、ESM周りの改善のための実験的な機能が実装、import.metafilenamedirnameが追加されるなど、Webとの互換性やESM利用に向けた改善機能が追加されました。

Node.js v21はバージョンが奇数なのでサポート期間は短く、2024年6月までとなっています。 日程についての最新の情報は以下のリポジトリをご確認ください。

github.com

また、この記事で紹介した機能のサンプルコードは以下のリポジトリにあります。参考になれば幸いです。

github.com

最後までお読みいただきありがとうございました。不備や質問がございましたら、@shisama_までメンションするかブコメなどでコメントください。

参考記事