Node.jsのERR_REQUIRE_ESMの原因と直し方

​​Node.js​ES Modules,JavaScript,npm,トラブルシュート

npm のパッケージを新しいメジャーバージョンに上げたあと、require() で読み込んだところで Error [ERR_REQUIRE_ESM] が出て止まるのは、そのパッケージが ES モジュール(import / export で書く形式)だけで配られているからです。古い Node.js の require() は ES モジュールを読めません。import() で読むか、自分のファイルを ES モジュールにすると、エラーは消えます。

このエラーが出るかどうかは、Node.js のバージョンで変わります。Node.js 22.12.0 と 20.19.0 からは require() で ES モジュールを読めるようになったため、同じコードでもエラーは出ません。ただし、代わりに別のエラーが出ることがあります。私は、ES モジュールだけのパッケージである chalk 5 を使って、4つのバージョンで同じファイルを実行し、出たエラーの全文と、直すと消えることを確かめました。

補足

検証環境は Ubuntu 24.04 で、Node.js 20.18.3、22.11.0、22.23.4、24.22.1 で同じファイルを実行しました。20.18.3 と 22.11.0 は nodejs.org で配布されている Linux 用のバイナリを使いました。パッケージは npm 11.19.1 で入れた chalk 5.6.2 と 4.1.2 です。Windows と macOS での動き、TypeScript を変換してから実行する場合、Jest や webpack などのツールの中で出る同じエラーは試していません。

エラーの全文(Node.js 20.18.3)

検証環境で npm init -y を実行し、package.json から type の行を消して(npm pkg delete type)、npm install chalk@5 で chalk 5.6.2 を入れました。chalk 5 の package.json には "type": "module" があり、exports には ES モジュールのファイルが1つだけ書かれています。

text
$ grep -E '"(version|type|exports)"' node_modules/chalk/package.json
	"version": "5.6.2",
	"type": "module",
	"exports": "./source/index.js",

この chalk を、CommonJS(require を使う、Node.js に元からある形式)で書いた index.js から読み込みます。

js
const chalk = require('chalk');

console.log(chalk.blue('hello'));

Node.js 20.18.3 で node index.js を実行すると、次のように出て止まりました(終了コードは1。パスは例示用の値に置き換えています)。

text
$ node index.js
/path/to/app/index.js:1
const chalk = require('chalk');
              ^

Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/app/node_modules/chalk/source/index.js from /path/to/app/index.js not supported.
Instead change the require of /path/to/app/node_modules/chalk/source/index.js in /path/to/app/index.js to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (/path/to/app/index.js:1:15) {
  code: 'ERR_REQUIRE_ESM'
}

Node.js v20.18.3

Error [ERR_REQUIRE_ESM] で始まる行は「index.js から ES モジュールの chalk/source/index.js を require() することはできない」という意味です。その次の Instead change … の行には直し方が書かれていて、「その require を、どの CommonJS のファイルでも使える動的な import() に書き換えてください」と言っています。上の index.js:1 と ^ は、止まった場所が1行目の require('chalk') であることを示しています。

Node.js 22.11.0 でも、同じ文面のエラーが出ました。ただ、場所の表示は index.js:315 と undefined になっていて、1行目の require を指していませんでした。止まった場所は、スタックの at Object.<anonymous> (/path/to/app/index.js:1:15) の行で分かります。

text
$ node index.js
/path/to/app/index.js:315
undefined
             ^

Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/app/node_modules/chalk/source/index.js from /path/to/app/index.js not supported.
Instead change the require of /path/to/app/node_modules/chalk/source/index.js in /path/to/app/index.js to a dynamic import() which is available in all CommonJS modules.
    at TracingChannel.traceSync (node:diagnostics_channel:315:14)
    at Object.<anonymous> (/path/to/app/index.js:1:15) {
  code: 'ERR_REQUIRE_ESM'
}

Node.js v22.11.0

Node.js のバージョンによる違い

同じ index.js を4つのバージョンで実行した結果は、次のとおりです。

Node.js node index.js の結果
20.18.3 ERR_REQUIRE_ESM で止まる
22.11.0 ERR_REQUIRE_ESM で止まる
22.23.4 TypeError: chalk.blue is not a function で止まる
24.22.1 TypeError: chalk.blue is not a function で止まる

公式ドキュメントの、require() で ES モジュールを読む機能の説明によると、この機能は Node.js 22.0.0 と 20.17.0 で入り、最初は --experimental-require-module を付けたときだけ使えました。Node.js 23.0.0、22.12.0、20.19.0 からは、何も付けなくても有効です。表で 22.11.0 までがエラーになり、22.23.4 からエラーが変わったのは、この違いによるものです。Node.js 24.15.0 からは、実験的な機能ではなくなりました。

ERR_REQUIRE_ESM の説明も、Node.js 22.12.0 と 20.19.0 から「同期的な ES モジュールは既定で require() で読めるようになった」とし、このエラーを非推奨(Deprecated)にしています。

新しい Node.js でもこのエラーを出すことはできます。Node.js 24.22.1 で --no-require-module を付けて、後で出てくる index-default.js を実行したところ、20.18.3 で index-default.js を実行したときと同じ ERR_REQUIRE_ESM が出ました。公式ドキュメントでは、この機能で予期しない問題が起きたときに無効にするための設定として説明されています。

新しい Node.js で出る「is not a function」

Node.js 22.23.4 と 24.22.1 では、require('chalk') は通りましたが、次の行で止まりました。次は Node.js 24.22.1 の出力です(パスは例示用の値に置き換えています)。

text
$ node index.js
/path/to/app/index.js:3
console.log(chalk.blue('hello'));
                  ^

TypeError: chalk.blue is not a function
    at Object.<anonymous> (/path/to/app/index.js:3:19)
    at Module._compile (node:internal/modules/cjs/loader:1929:14)
    at Object..js (node:internal/modules/cjs/loader:2060:10)
    at Module.load (node:internal/modules/cjs/loader:1651:32)
    at Module._load (node:internal/modules/cjs/loader:1443:12)
    at wrapModuleLoad (node:internal/modules/cjs/loader:261:19)
    at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)
    at node:internal/main/run_main_module:33:47

Node.js v24.22.1

require() で ES モジュールを読むと、返ってくるのはモジュールの中身そのものではなく、書き出された名前をまとめたオブジェクト(モジュール名前空間オブジェクト)です。export default で書き出したものは、その default に入ります。返ってきたオブジェクトのキーを表示すると、default のほか、Chalk や __esModule などが並び、blue はありませんでした。mod.default.blue の型は function でした。

js
const mod = require('chalk');

console.log(Object.keys(mod));
console.log(typeof mod.default.blue);
text
$ node inspect.js
[
  'Chalk',
  '__esModule',
  'backgroundColorNames',
  'backgroundColors',
  'chalkStderr',
  'colorNames',
  'colors',
  'default',
  'foregroundColorNames',
  'foregroundColors',
  'modifierNames',
  'modifiers',
  'supportsColor',
  'supportsColorStderr'
]
function

そこで、.default を付けて書き換えた index-default.js を用意しました。

js
const chalk = require('chalk').default;

console.log(chalk.blue('hello'));

Node.js 22.23.4 と 24.22.1 では hello と表示され、エラーも警告も出ませんでした。一方、20.18.3 と 22.11.0 では ERR_REQUIRE_ESM が出ました(Instead change … の行は Instead change the require of index.js in … と、ファイル名だけの書き方でした)。この書き方が使えるのは、require() で ES モジュールを読める Node.js だけです。

Node.js 22.11.0 でも --experimental-require-module を付けると、index-default.js は hello と表示しました。ただし、次の警告も出ました((node:1691) はプロセス番号です)。

text
$ node --experimental-require-module index-default.js
hello
(node:1691) ExperimentalWarning: Support for loading ES Module in require() is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)

「ES モジュールを require() で読む機能は実験的なもので、いつ変わるか分からない」という意味です。公式ドキュメントによると、この警告は Node.js 22.13.0 と 20.19.0 から、既定では出なくなりました。

直し方とエラーが消えたこと

どの方法も、エラーが消えて hello と表示されることを確かめました。どのバージョンでも動かしたいなら、import() か ES モジュールへの書き換えを選びます。

CommonJS のまま import() で読む

エラーの Instead change … の行が勧めている方法です。import() は Promise を返すので、async の関数の中で await して受け取ります。default の書き出しは、default という名前で取り出します。

js
(async () => {
  const { default: chalk } = await import('chalk');
  console.log(chalk.blue('hello'));
})();

この index-dynamic.js は、20.18.3、22.11.0、22.23.4、24.22.1 のすべてで hello と表示しました。package.json もほかのファイルも変えずに済みますが、chalk を使う処理を await のあとに置く必要があります。

自分のファイルを ES モジュールにする

ES モジュールのパッケージは、ES モジュールのファイルから import で読むのが本来の使い方です。拡張子を .mjs にした index.mjs を用意しました。

js
import chalk from 'chalk';

console.log(chalk.blue('hello'));

node index.mjs は、4つのバージョンのすべてで hello と表示しました。プロジェクト全体を ES モジュールにするなら、拡張子は .js のまま、package.json に "type": "module" を書く方法もあります(公式ドキュメントの "type" の説明)。その場合は、ほかのファイルの require も import に書き換えます。

CommonJS で配られている版に戻す

すぐに書き換えられないときは、パッケージを CommonJS の版に戻す手もあります。npm install chalk@4 で 4.1.2 を入れると、最初の index.js のまま、20.18.3 でも 24.22.1 でも hello と表示されました。ただし、この場合は chalk の古いメジャーバージョンを使い続けることになります。

トップレベル await があると require() では読めない

新しい Node.js でも、require() で読めない ES モジュールがあります。関数の外で await を書いた(トップレベル await の)モジュールです。検証のために、次の index.js を持つ tla-pkg というパッケージを node_modules の中に作りました(package.json は "type": "module")。

js
const config = await Promise.resolve({ name: 'neko' });

export default config;

これを require('tla-pkg').default で読むと、Node.js 24.22.1 では ERR_REQUIRE_ESM ではなく、ERR_REQUIRE_ASYNC_MODULE が出ました(- /path/to/app/tla.js の後の at … で始まる行から最後までは省略し、パスは例示用の値に置き換えています)。

text
$ node tla.js
node:internal/modules/esm/module_job:313
    throw new ERR_REQUIRE_ASYNC_MODULE(filename, parent, locations);
    ^

Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead. To see where the top-level await comes from, use --experimental-print-required-tla.
Required module: /path/to/app/node_modules/tla-pkg/index.js
Require stack:
- /path/to/app/tla.js

「トップレベル await を含む ES モジュールには require() を使えない。代わりに import() を使ってください」という意味です。Node.js 22.23.4 でも同じ1行目のエラーが出ましたが、その下の行は From /path/to/app/tla.js と Requiring /path/to/app/node_modules/tla-pkg/index.js で、24.22.1 とは書き方が違いました。20.18.3 では、chalk のときと同じ ERR_REQUIRE_ESM でした。

ERR_REQUIRE_ASYNC_MODULE の説明のとおり、読もうとしたモジュールが非同期だったときのエラーです。直し方は、メッセージのとおり import() です。上と同じ形で await import('tla-pkg') に書き換えると、Node.js 24.22.1 で neko と表示されました。

ポイント

Node.js を 22.12.0 以降や 20.19.0 以降に上げると ERR_REQUIRE_ESM は出なくなりますが、require() の戻り値の形が変わるので、.default を付けるなどの書き換えは必要です。トップレベル await を含むパッケージは、新しい Node.js でも import() でしか読めません。

​Posted by 86