ES Module(ESM)から名前付き import をしたら、次のどちらかのエラーで止まることがあります。
SyntaxError: The requested module './x.js' does not provide an export named 'foo'
SyntaxError: Named export 'foo' not found. The requested module './x.cjs' is a CommonJS module, which may not support all module.exports as named exports.
前者は「その名前のエクスポートが無い」、後者は「相手が CommonJS で、その名前を名前付きエクスポートとして扱えない」という表示です。名前のタイプミスでなければ、原因はたいてい CommonJS モジュールを ESM から名前付き import していることにあります。
なぜ CommonJS では名前付き import が通らないことがあるのか
CommonJS モジュールは module.exports という 1 つのオブジェクトをエクスポートする仕組みです。ESM の名前付きエクスポートとは形が違います。それでも多くの場合は名前付き import が通ります。Node が CommonJS のコードを静的に解析して、module.exports.foo = ... のように直接たどれる代入を名前付きエクスポートとして拾ってくれるからです(この解析は Node 20 / 22 のドキュメントでは cjs-module-lexer と説明されています)。
問題は、この解析で拾えない書き方のときです。たとえばキーを計算して代入する形は解析できません。
// 解析できる(import { foo } が通ることが多い)
module.exports.foo = () => {};
// 解析できない(import { foo } は "not found" になる)
const name = 'foo';
module.exports[name] = () => {};
だから 「あるパッケージでは名前付き import できるのに、別の CommonJS パッケージだと落ちる」 という差が出ます。相手のエクスポートの書き方しだいで、静的解析が名前を拾えたり拾えなかったりするためです。自分で書き分けているわけではないので、原因が分かりにくいエラーです。
再現(最小構成)
静的解析で拾えない形でエクスポートする CommonJS モジュールを用意します。
// cjs-dep.cjs(.cjs は常に CommonJS)
const name = 'greet';
module.exports[name] = function () {
return 'hi from CJS';
};
これを ESM から名前付き import します。
// index.js(package.json に "type": "module"。ESM 扱い)
import { greet } from './cjs-dep.cjs';
console.log(greet());
実行すると Named export 'greet' not found ... が出て、終了コードは 1 になります。
解決:既定 import で受け取ってから取り出す
CommonJS モジュールは、既定 import で module.exports を丸ごと受け取り、そこから必要なものを取り出します。エラーメッセージ自身もこの形を案内しています。
// index.js(既定 import で受けて分割代入)
import pkg from './cjs-dep.cjs';
const { greet } = pkg;
console.log(greet());
これで終了コード 0 で動きます。pkg が module.exports そのものなので、静的解析に頼らず、実行時に module.exports から取り出せます。取り出した値が undefined になるなら、CommonJS 側にそのプロパティが実際にあるか(名前のタイプミスが無いか)を確認してください。
仕組み:名前付き import は「読み込み前」に解決される
ESM の名前付き import は、コードを実行する前の段階で「その名前が本当にあるか」を照合します。相手が ESM なら export 文を見れば分かりますが、相手が CommonJS だと export 文が無いので、Node は静的解析で名前付きエクスポートを推定します。推定できなかった名前は「無い」と判定され、実行前に SyntaxError になります。
既定 import はこの名前ごとの照合をしません。module.exports オブジェクトを 1 つ受け取るだけなので、プロパティが実行時にどう作られていても取り出せます。
切り分け(うまくいかないとき)
- 相手が自分の ESM ファイルなのに出る:
does not provide an export named 'foo'の場合、その ESM に本当にexportがあるか、名前(大文字小文字・default か名前付きか)が合っているかを確認します。export defaultはimport foo from ...、名前付きはimport { foo } from ...です。 - 既定 import した
pkgが関数そのものだった:module.exports = function () {}の形の CommonJS では、pkg自体が関数です。pkg.fooではなくpkg()として使います。 - ESM のファイルで
requireが無いと言われた:症状が違うケースです。require is not defined in ES module scope を参照してください。 - CommonJS を
require()したら別のエラーになった:ESM から CJS を読むのではなく、CJS から ESM をrequire()して落ちたなら require() of ES Module not supported(ERR_REQUIRE_ESM) を参照してください。