結論:ESM専用パッケージを CommonJS から require できないために起きる
ERR_REQUIRE_ESM は、ESM(ES Modules)専用として公開されたパッケージを、CommonJS(CJS)プロジェクトの require() で読み込もうとしたときに出る。最短の回避策は、その箇所を require() から await import()(dynamic import)に書き換えること。長期的にはプロジェクト自体を ESM 化するのが筋がよい。
// NG(CJSから ESM専用パッケージを require)
const chalk = require('chalk') // → ERR_REQUIRE_ESM
// OK(dynamic import に置き換える)
const chalk = (await import('chalk')).default
以下、エラーの読み方・原因・状況別の5つの対策を順に示す。
エラーメッセージの全文と読み方
Error [ERR_REQUIRE_ESM]: require() of ES Module
/path/to/node_modules/chalk/source/index.js not supported.
Instead change the require of index.js to a dynamic import()
which is available in all CommonJS modules.
メッセージはそのまま「require() するな、dynamic import() を使え」と言っている。自分のコードのバグではなく、依存パッケージの配布形態が ESM 専用に変わったことが原因であるケースがほとんどだ。
原因:人気パッケージが続々と「ESM専用(pure ESM)」へ移行した
chalk・node-fetch をはじめ、多くのユーティリティ系パッケージがメジャーアップデートで CJS 対応を捨て、ESM 専用配布に切り替えた。CJS プロジェクトのまま npm update するとある日突然このエラーに遭遇する。
| パッケージ | ESM専用になったバージョン |
|---|---|
| chalk | v5.0.0 以降 |
| node-fetch | v3.0.0 以降 |
| p-limit | v4.0.0 以降 |
| globby | v13.0.0 以降 |
| execa | v6.0.0 以降 |
| got | v12.0.0 以降 |
| ora | v6.0.0 以降 |
| nanoid | v4.0.0 以降 |
| find-up | v6.0.0 以降 |
| strip-ansi | v7.0.0 以降 |
判別法:そのパッケージの package.json に "type": "module" があり、exports が import 条件しか持たない(require 条件がない)なら ESM 専用だ。
対策1(即効):require を dynamic import() に置き換える
CJS のままでよく、その場をしのぎたいなら await import() が最短。CJS モジュール内でも dynamic import は使える。
// before
// const chalk = require('chalk')
// after(トップレベルが async でないなら関数内で)
async function main() {
const { default: chalk } = await import('chalk')
console.log(chalk.green('OK'))
}
main()
注意:dynamic import は Promise を返すので、読み込み箇所が同期前提だと連鎖的に async 化が必要になる。デフォルトエクスポートは .default で取り出す。
対策2(最速の応急処置):CJS対応の最終バージョンに据え置く
すぐに直したい・依存箇所が多いなら、CJS をサポートしていた最後のメジャーに固定するのが手堅い。
npm install chalk@4 # v4 は CJS 対応(require できる)
npm install node-fetch@2 # v2 は CJS 対応
{
"dependencies": {
"chalk": "^4.1.2",
"node-fetch": "^2.7.0"
}
}
ただしこれは時間稼ぎ。古いバージョンはいずれセキュリティ更新が止まるため、対策3か4で本筋の解決を進める前提にする。
対策3(本筋):プロジェクトを ESM 化する
新しめのプロジェクトなら package.json に "type": "module" を付けて ESM 化するのが正攻法。require をやめて import で書けば ERR_REQUIRE_ESM は根本からなくなる。
{
"type": "module"
}
// ESM ならそのまま import できる
import chalk from 'chalk'
ただし ESM 化すると今度は __dirname / require が使えなくなって別のエラーが出る。これは典型的なハマりどころで、Node.js ESM で __dirname が not defined になる原因と解決法 で対処法をまとめている。createRequire(import.meta.url) で require を部分的に復活させることも可能だ。
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const legacy = require('some-cjs-only-package') // CJSのみのパッケージはこれで読める
対策4(TypeScript):実行系と tsconfig を ESM に合わせる
TypeScript プロジェクトでは、TS のコンパイル結果が CJS(require)になっているのに依存が ESMだと ERR_REQUIRE_ESM になる。tsconfig.json を NodeNext に揃える。
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022"
}
}
開発時は tsx などネイティブESMで動く実行系を使うと、ビルド設定の不一致による require 変換を避けられる。
npm install -D tsx
npx tsx src/index.ts
なお ESM 専用パッケージと混同しやすい別エラーに ERR_PACKAGE_PATH_NOT_EXPORTED(exports フィールドで公開されていないパスを import)がある。こちらは package.json の exports で import できない時の原因と対策 を参照。
対策5:バンドルして CJS に取り込む
CLI ツールなど配布物を1ファイルにまとめたい場合は、esbuild / tsup などのバンドラで ESM 依存ごと取り込んでしまう手もある。
npx esbuild src/index.ts --bundle --platform=node --format=cjs --outfile=dist/index.cjs
バンドラが ESM 依存を解決して1つの CJS 出力に変換するため、実行時の ERR_REQUIRE_ESM を回避できる。ライブラリではなくアプリ/CLIの配布で有効。
まとめ
| 状況 | 推奨する対策 |
|---|---|
| その場をしのぎたい(CJSのまま) | 対策1: await import() に置換 |
| とにかく今すぐ直す | 対策2: chalk@4 / node-fetch@2 に据え置き |
| 新規・移行を進められる | 対策3: "type": "module" で ESM 化 |
| TypeScript プロジェクト | 対策4: module: "NodeNext" + tsx |
| CLI/配布物 | 対策5: esbuild/tsup で CJS にバンドル |
ERR_REQUIRE_ESM は「依存が ESM 専用になった」というエコシステム全体の流れが背景にある。応急処置(対策1・2)で止血しつつ、中期的には ESM 化(対策3・4)へ寄せていくのが、再発しない方針だ。
