別にしんどくないブログ

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

Node.js v23の主な変更点

nodejs logo
引用元: https://nodejs.org/en/about/branding

2024年10月17日(日本時間)にリリースされたNode.js v23の主な変更点を紹介します。

nodejs.org

記事内のサンプルコードはすべて以下のリポジトリにあります。

github.com

require()でESMを読み込む機能が安定版に

github.com

v22から追加されたrequire()でESMなファイルを読み込む機能ですが、以下のように--experimental-require-moduleフラグを付けて実行する必要がありました。しかし、v23からはフラグなしでも実行可能になりました。

// Node.js v22以前はフラグが必要
node --experimental-require-module index.cjs

// Node.js v23以降はフラグが不要
node index.cjs

以下のようにESMをCJSのファイルでrequire()を使って読み込むことができます。

// index.cjs
const { module } = require('./module.mjs`);

ただし、top-level awaitを使っているESMファイルを読み込むとエラーが発生します。これはrequire()はあくまで同期的なモジュールの読み込みしかできないのに対して、Top-level Aawaitは非同期でなければいけないためです。

// ESMファイル

// top-level awaitを使っている
const response = await fetch("https://api.example/xxx');
export const text = await response.text();
// CJSファイル
const { text } = require('./module.mjs');
// => top-level awaitを用いるファイルを読み込もうとしたのでERR_REQUIRE_ASYNC_MODULEエラーが発生する

また、require(esm)が利用可能かどうかの判定をするためにはprocess.features.require_moduleが有効です。

if (process.features.require_module) {
    const foo = require("package-a").foo;
    console.log(foo());
    // module-sync
}

top-level awaitを使うファイルにさえ気をつければCJSな環境でもESMのファイルを読み込むことができてとても便利です。

Node.js v22の記事でも説明しているので、そちらも興味があればお読みください。

shisama.hatenablog.com

"module-sync"をexports conditionに追加

github.com

前述したrequire(esm)に関連するのですが、package.jsonexportsフィールドに新しく"module-sync"を設定できるようになりました。
これはrequire()で読み込まれるモジュールを指定することができます。

package.jsonexportsフィールドは条件に応じて読み込ませるモジュールを変更することができる機能です。

{
  "type": "module",
  "main": "./index.js",
  "exports": {
    ".": {
      "require": "./index.cjs",
      "default": "./index.js"
    }
  }
}

package.jsonに上記のような設定がされているnpmパッケージをESMから読み込もうとしたときは./index.jsが読み込まれ、CJSから読み込もうとしたときは./index.cjsが読み込まれます。
v23ではこのexports"module-sync"を指定することができます。

{
  "type": "module",
  "main": "./index.js",
  "exports": {
    ".": {
      "module-sync": "./module.js",
      "default": "./index.js"
    }
  }
}

たとえば、以下のようなファイルがあったとします。

// index.js
await Promise.resolve();

export const foo = () => {
  return 'default';
};

こちらはtop-level awaitを使っているのでrequire()で読み込むとエラーになります。なので、package.jsonexportsでは"default""import"に指定します。

{
  "type": "module",
  "main": "./index.js",
  "exports": {
    ".": {
      "default": "./index.js"
    }
  }
}

次にrequire()で読み込めるような以下のファイルがあったとします。

// module.js
export const foo = () => {
  return 'module-sync';
};

こちらはtop-level awaitを利用していないので、require()で読み込んでもエラーになりません。ですから"module-sync"に設定します。

{
  "name": "package-a",
  "type": "module",
  "main": "./index.js",
  "exports": {
    ".": {
      "module-sync": "./module.js",
      "default": "./index.js"
    }
  }
}

上記のような"module-sync"が設定されたパッケージをrequire()で読み込むコードをNode.js v23で実行するとmodule.jsが実行されます。

const { foo } = require("package-a");
console.log(foo());
// => bar

こちらの"module-sync"はまだ実験的な機能ですので、実行時に以下の警告が表示されます。

(node:73758) ExperimentalWarning: Support for loading ES Module in require() is an experimental feature and might change at any time

Node.js v22以前の"module-sync"に対応していないバージョンでrequire()を使って読み込むと、./index.jsを読み込んでしまい、ERR_REQUIRE_ASYNC_MODULEエラーが発生します。

require(esm)"module-sync"によりESM/CJSの互換性は向上するので、npm packageの作成者は利用をおすすめします。

node --run が安定版へ

github.com

Node.jsの実行オプションである--runオプションはnpm runと同じようにpackage.json"scripts"に記載したコマンドを実行することができます。

Node.js v22.0.0で実験的な機能として追加された機能ですが、v23からは安定版として利用可能です。

たとえば、以下のように"test"コマンドが定義されていたとします。

  "scripts": {
    "test": "node --test"
  },

以下のようにNode.jsを実行すると"test"を実行することができます。

node --run test

Node.jsのテストランナー(node:test)のカバレッジ対象をglobで指定できるフラグの追加

github.com

Node.js v18でNode.js本体にもテストランナーが追加され、v20で安定版となりました。 カバレッジ取得についてはまだ実験的な機能ですが可能です。以下のようにフラグを付けて実行します。

node --test --experimental-test-coverage

v23からカバレッジを取る対象または対象外のファイルをglobパターンで指定できるようになりました。

カバレッジ対象を指定するには--test-coverage-includeを使います。たとえば、srcフォルダの中のファイルだけカバレッジを取りたい場合は以下のように指定します。

node --test --experimental-test-coverage --test-coverage-include=src/**/*.js

カバレッジ対象外を指定するには--test-coverage-excludeを使います。たとえば、.test.jsの拡張子が付くファイルを対象外にしたい場合は以下のように指定します。

node --test --experimental-test-coverage --test-coverage-exclude=**/*.test.js

Windows 32-bitはサポート対象外へ

github.com

Node.js v23から32bit版のWindowsはサポート対象外となりました。Node.jsのビルド対象からも削除されたので、Node.jsのバイナリが提供されることはありません。
もしWindowsの32bit版をお使いの方は環境を移行するかNode.jsのソースコードを修正して自分でビルドするしかありません。

v23のEOL予定について

今回紹介したNode.js v23は2025年6月でメンテナンスが終了する予定です。
今回紹介した機能は次のv24(2025年4月リリース予定)でも利用可能なので、そちらへ移行するようにしてください。 日程についての最新の情報は以下のリポジトリをご確認ください。

github.com

まとめ

今回はrequire(esm)がフラグなしで利用可能になったことや"module-sync"を使ってCJS向けに安全にパッケージを提供できる仕組みが追加されたことが大きいと思います。これによりより一層ESMを利用する流れになっていくと良いと思います。

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

参考記事