echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Node.js】Node.js 22の新機能を確認する

※このブログは、4/30(火) 19:00~開催の「JAWS-UG札幌 オンラインもくもく会 #95」にて記載した内容になっております。

jawsug-sapporo.connpass.com

はじめに

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])

サンプルソースを下記に示します。(なお、カレントフォルダには以下ファイル&フォルダが存在するものとします)

  • node_modules
  • .gitignore
  • index.js
  • index.mjs
  • package-lock.json
  • package.json
  • sample.txt
// 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を継承しています。