※このブログは、4/30(火) 19:00~開催の「JAWS-UG札幌 オンラインもくもく会 #95」にて記載した内容になっております。
はじめに
4/24(水)に、Node.jsの最新版であるNode.js 22がリリースされました。
そこで今回は、さっそくNode.js 22の新機能についてチェックしようと思います。(ちなみに、Node.js 22はおそらく10月ごろからLTSになるものと思われます)
参考サイト
新機能一覧
新機能の概要はこちら。
概要 | 説明 | experimental | 備考 |
---|---|---|---|
V8 Update to 12.4 | V8エンジンが12.4にアップデートし、それに伴いWASMのガベージコレクションや各種メソッドが追加された | × | 各種メソッドについては「Pick Up」で説明 |
Maglev | Maglevコンパイラがデフォルトで有効になった | × | |
Support require()ing synchronous ESM graphs | CommonJSの「require」で、ES Moduleのソースを読み込めるようになった | 〇 | ただし条件あり。詳しくは「Pick Up」で説明 |
Running package.json scripts | node.jsから直接package.jsonのscriptを実行できるようになった | 〇 | 詳しくは「Pick Up」で説明 |
Stream default High Water Mark | High Water Mark(stream処理における内部バッファの閾値)の初期値が16KiBから64KiBに増加し、パフォーマンスが向上 | × | |
Watch Mode | Watchモード(node --watch)がexperimentalからstableに | × | |
WebSocket | ブラウザ互換のWebSocketがexperimentalからstableになり、デフォルトで有効に | × | |
glob and globSync | ファイルの検索時に使うglobパターン指定用関数が追加 | 〇 | 詳しくは「Pick Up」で説明 |
Improve performance of AbortSignal creation | AbortSignalインスタンス生成時のパフォーマンスが向上 | × |
Pick Up
ここからは、一部新機能について紹介します。
V8 Update to 12.4
ブラウザではサポートされていた下記の機能が使用可能になりました。
機能 | 説明 | 備考 |
---|---|---|
Array.fromAsync | 反復可能(=itelable)オブジェクトから値のみの(=シャローコピーされた)配列を返します | |
Setオブジェクト | 型やプリミティブ/オブジェクトを問わず、いろいろな一意の値を格納できる | 「一意の値」なので、重複した値は格納できない |
Iterator helpers | 反復可能オブジェクト(=Iterator)の各種インスタンスメソッドを使用可能に | filter, find, mapなど、Array.prototypeで使用可能なメソッドと同じものが多い |
なおこれらについて、ソースコードや説明に関してはMDN Web Docsにかなり詳しく書いてあるので(特にSetクラス)、そちらを参照して下さい。(上表の「機能」にリンクを貼っておきます)
Setオブジェクトのメソッド紹介
- いずれのメソッドも、戻り値としてSetオブジェクトを返します
- A, BはどちらもSetオブジェクト
メソッド | 戻り値のSetオブジェクトに含まれる値 | 備考 |
---|---|---|
A.intersection(B) | A, B両方に含まれている値 | 積集合 |
A.union(B) | A, Bいずれかに含まれている値 | 和集合。なおA. B両方に含まれる値は1つにまとめられます |
A.difference(B) | AにあってBにない値 | 差集合 |
A.symmetricDifference(B) | AかBのいずれか一方にしかない値 | 対象差集合。AとB両方が持つ値は除去されます |
Iterator helpersの一部メソッドの紹介
Iterator helpersのメソッドのうち、Array.prototypeが持っていないものを紹介します。
メソッド | 説明 | 備考 |
---|---|---|
drop(limit) | 元のIteratorから、先頭limit個分の要素を除去したIterator helperを返却します | Arrayで言うArray.slice(limit) |
take(limit) | 元のIteratorから、先頭limit個分の要素のみを持つIterator helperを返却します | Arrayで言うArray.slice(0, limit) |
toArray() | Iteratorから通常の配列(=Arrayクラスの配列)を返します | Array.from(iterator)と同じ |
小ネタ:Array.fromAsyncでファイルを読み込む
MDN Web Docsにも記載がある通り、ReadableStreamは非同期反復可能オブジェクトです。
つまり「fs.createReadStream*1で読み込んだストリーム(≒ファイルの内容)をArray.fromAsyncで取得する」という事が可能です。(実用的かどうかは別として)
以下がサンプルソースになります。
const fs = require('fs'); // sample.txtには「I am Node.js 22.」というテキストが書いてある async function getAsyncIterable() { const fileName = 'sample.txt'; const stream = fs.createReadStream(fileName); stream.setEncoding('utf8'); const result = await Array.fromAsync(stream); // 戻り値は配列なので、配列の要素を取得する return result[0]; } (async () => { const result = await getAsyncIterable(); console.log(result); })();
上記ソースを実行すると、以下の結果が返ってきます。
$ node index.js
I am Node.js 22.
Support require()ing synchronous ESM graphs(Experimental)
CommonJSの「require」にて、ES Module形式のモジュール(関数なりクラスなり)を読み込むことが可能になりました。
ただし読み込み元のES Module形式のファイルは、以下を両方満たしている必要があります。
- 対象のpackage.jsonに「"type": "module"」の定義がある、または拡張子が「*.mjs」である。(=ES Module形式で書く際のルール)
- すべての処理が同期処理である(=awaitなど、非同期処理を含んでいない)
また現段階ではこの機能は「Experimental(=実験的)」であり、実行時に --experimental-require-module
オプションをつける必要があります。
サンプルソースは以下になります。
// point.mjs export function myPow(a, b) { return Math.pow(a, b); } function mySqrt(a) { return Math.floor(Math.sqrt(a)) }; export default mySqrt;
// index.js const required = require('./index.mjs'); async function requiredSample() { console.log(`mySqrt is ${required.default(9)}`); console.log(`myPow is ${required.myPow(2, 3)}`); const imported = await import('./index.mjs'); console.log(imported === required); console.log(`mySqrt is ${imported.default(25)}`); console.log(`myPow is ${imported.myPow(3, 4)}`); } (async () => { await requiredSample(); })();
上記index.jsは、下記コマンドで実行し、その結果は以下の通りです。
$ node --experimental-require-module index.js mySqrt is 3 myPow is 8 true mySqrt is 3 myPow is 8
importedとの比較結果がtrueで、各関数の実行結果も同じなので、requireとimportで全く同じである事が分かると思います。
Running package.json scripts(Experimental)
package.jsonの「scripts」について、いままではnpm/yarnなどパッケージマネージャー経由で実施していましたが、nodeから直接実行できるようになりました。
なお、こちらも現段階ではExperimentalとなっています。
具体的なソース&実行例は以下の通りです。
// package.json { "scripts": { "hoge": "echo hogehoge" } }
$ node --run hoge
hogehoge
glob and globSync(Experimental)
何かの処理対象ファイルを指定する際などに使用可能なglobパターンについて、下記関数が追加され、Node.jsだけで完結できるようになりました。
- fs.glob(pattern[, options], callback)
- fs.globSync(pattern[, options])
- fsPromises.glob(pattern[, options])
サンプルソースを下記に示します。(なお、カレントフォルダには以下ファイル&フォルダが存在するものとします)
// index,js const fs = require('fs'); const fsp = require('node:fs/promises'); // いずれの関数も「package.json」と「package-lock.json」が // ヒットすることを確認しています。 // fs.globとfs.globSyncの戻り値はファイル名の配列。 // 違いは同期関数かどうかだけです function getAsyncGlobPathFs() { fs.glob('./*kag*.js*', (err, matched) => { if (err) throw err; console.log(matched); }); } function getSyncGlobPath() { const matched = fs.globSync('./*kag*.js*') console.log(matched); } // fsPromises.glob戻り値は非同期反復可能オブジェクト(AsyncIterator)なので、 // ファイル名はそこからさらにArray.fromAsyncなどで取得する必要があります。 async function getAsyncGlobPathFsPromise() { const matched = await Array.fromAsync(fsp.glob('./*kag*.js*')); console.log(matched); } (async () => { getAsyncGlobPathFs(); getSyncGlobPath(); await getAsyncGlobPathFsPromise(); })();
$ node index.js [ 'package-lock.json', 'package.json' ] [ 'package-lock.json', 'package.json' ] [ 'package-lock.json', 'package.json' ]
個人的には、これが一番実用的かなと思いました。
まとめ
以上、Node.js 22の新機能でした。
今回も機能面・性能面で色々追加されており、Node.jsでの開発がもっと楽になるといいですね。
それでは、今回はこの辺で。
*1:fs.createReadStreamの戻り値fs.ReadStreamは、stream.Readableを継承しています。