ERR_REQUIRE_ESM修正: Node.jsでrequire()によるESモジュールのロードが非対応

intermediate💚 Node.js2026-03-22| Node.js 12以降、npm/pnpm/yarnプロジェクト、ESM専用パッケージ(node-fetch v3、chalk v5、nanoid v4+など)をインポートするCommonJSプロジェクト

Error Message

Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/module.mjs not supported
#nodejs#esm#require#commonjs#module

状況の説明

深夜2時。デプロイが失敗するか、依存関係のバージョンを上げた後にローカルビルドが壊れた。スタックトレースはこんな感じだ:

Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/node_modules/node-fetch/src/index.js not supported.
/path/to/node_modules/node-fetch/src/index.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package as ES modules.
Instead either rename /path/to/node_modules/node-fetch/src/index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /path/to/node_modules/node-fetch/src/index.js.
    at Object. (/your/project/src/api.js:3:18)

コードは何も変えていない — パッケージのバージョンを変えただけだ。これがCommonJS対ESMの互換性の壁というやつだ。

実際に何が起きているのか

Node.jsは2つのモジュールシステムをサポートしている:

  • CommonJS (CJS)require() / module.exports を使う — 従来のNode.js方式
  • ESM (ES Modules)import / export を使う — モダンな標準

問題は、CJSはESMモジュールをrequire()できないということだ。これは絶対だ。2021年頃から、node-fetch v3、chalk v5、nanoid v4、got v12、execa v6などの人気パッケージがCJSサポートを完全に廃止し、ESM専用になった。これらのうち一つでもアップグレードした瞬間、require()を使っているプロジェクトは壁にぶつかる。

まず根本原因を特定する

修正方法を選ぶ前に、プロジェクトがどのモジュールシステムを使っているか確認しよう:

cat package.json | grep '"type"'
  • typeフィールドなし、または"type": "commonjs" → プロジェクトはCJS
  • "type": "module" → プロジェクトはESM

次に問題のパッケージを確認する:

cat node_modules/node-fetch/package.json | grep '"type"'

"type": "module"と書いてあれば、そのパッケージはESM専用だ — require()はできない。これで問題の正体がわかった。以下の修正方法から選んでほしい。

クイックフィックス:最後のCJSバージョンに固定する

5分以内に動かしたい?パッケージを最後のCommonJS互換バージョンに固定しよう。コードの変更は不要だ。

# node-fetch: 最後のCJSバージョンはv2
npm install node-fetch@2

# chalk: 最後のCJSバージョンはv4
npm install chalk@4

# nanoid: 最後のCJSバージョンはv3
npm install nanoid@3

# got: 最後のCJSバージョンはv11
npm install got@11

# execa: 最後のCJSバージョンはv5
npm install execa@5

修正を確認する:

node -e "const fetch = require('node-fetch'); console.log(typeof fetch);"

functionと表示されるはずだ。ブロックが解消された。リリースしよう。

恒久的な修正オプション1:動的import()を使う

プロジェクト全体を変換せずに最新バージョンのパッケージを使いたい?動的import()が橋渡しになる。CJSファイルはimport()を呼び出せる — ファイルの先頭に静的なimportキーワードを使えないだけだ。

// 修正前(動かない)
const fetch = require('node-fetch');

// 修正後(CJSファイルで動作する)
async function fetchData(url) {
  const { default: fetch } = await import('node-fetch');
  const res = await fetch(url);
  return res.json();
}

{ default: fetch }の分割代入に注目してほしい。CJSから動的importでESMのデフォルトエクスポートにアクセスする場合、値は直接ではなくdefaultプロパティに入る。

動作確認のクイックテスト:

node -e "
async function test() {
  const { default: fetch } = await import('node-fetch');
  const res = await fetch('https://httpbin.org/get');
  console.log(res.status);
}
test();
"

恒久的な修正オプション2:プロジェクトをESMに変換する

長期的にクリーンな解決策を求めるなら、プロジェクト全体をESMに移行しよう。最初の作業は増えるが、互換性の問題を完全に解消できる。

ステップ1:package.jsonを更新する

{
  "type": "module"
}

ステップ2:require/module.exportsをimport/exportに置き換える

// 修正前(CJS)
const express = require('express');
const { readFileSync } = require('fs');
module.exports = { myFunction };

// 修正後(ESM)
import express from 'express';
import { readFileSync } from 'fs';
export { myFunction };

ステップ3:__dirnameと__filenameを修正する(ESMでは存在しない)

// __dirnameが必要なファイルの先頭にこれを追加する
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

ステップ4:動的requireを修正する

// 修正前
const config = require(`./configs/${env}.js`);

// 修正後
const { default: config } = await import(`./configs/${env}.js`);

プロジェクトを実行して何が壊れるか確認しよう:

node src/index.js

さらにいくつかのエッジケースが出てくるはずだ — JSONインポート、require.resolve、ESMサポートのない古いパッケージなど。表面化したら順番に修正していこう。

恒久的な修正オプション3:バンドラーやトランスパイラーを使う

手動移行のリスクが高い?バンドラーにCJS/ESMの変換を任せよう。

# esbuild — 最速の選択肢
npm install -D esbuild
npx esbuild src/index.js --bundle --platform=node --outfile=dist/index.cjs

# tsx — TypeScriptプロジェクトに最適
npm install -D tsx
npx tsx src/index.ts

TypeScriptユーザーはESMスタイルのインポートを書きながら、CJS出力のままにできる:

// tsconfig.json
{
  "compilerOptions": {
    "module": "CommonJS",
    "esModuleInterop": true
  }
}

特殊ケース:Jestで実行する場合

JestはデフォルトでCJSモードで動くため、ESMパッケージはテストも壊す。2つの選択肢がある:

# オプション1:JestでESMサポートを有効にする
NODE_OPTIONS='--experimental-vm-modules' npx jest

# オプション2:JestにESMパッケージを変換するよう指示する
module.exports = {
  transformIgnorePatterns: [
    'node_modules/(?!(node-fetch|chalk|nanoid)/)',
  ],
};

オプション1のほうがセットアップが簡単だ。オプション2は複数のESM専用パッケージが関わる場合に細かい制御ができる。

動作確認

修正後にこの3つのチェックを実行しよう:

# 1. エントリーポイントを直接実行する
node src/index.js

# 2. テストスイートを実行する
npm test

# 3. 特定のインポートをスポットチェックする
node -e "import('node-fetch').then(m => console.log('OK:', typeof m.default))"

出力のどこにもERR_REQUIRE_ESMが出ない?完了だ。

どの修正を選ぶか

  • 5分以内に直したい:最後のCJSバージョンに固定する
  • 最新パッケージを使いたい、リファクタは最小限に:動的import()を使う
  • 新規プロジェクト、またはリファクタできる:プロジェクトをESMに変換する
  • TypeScriptプロジェクト:tsonfigで"module": "CommonJS"esModuleInterop: trueを設定する

Related Error Notes