← 記事一覧に戻る

Node.js で ERR_REQUIRE_ESM が出る・require() が動かない時の原因と5つの対策【chalk v5 / node-fetch v3】

結論: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)」へ移行した

chalknode-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" があり、exportsimport 条件しか持たない(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_EXPORTEDexports フィールドで公開されていないパスを 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)へ寄せていくのが、再発しない方針だ。