Node.jsのERR_REQUIRE_ESMを解決する|package typeとNode versionを照合

Node.jsのESM読込失敗を実行version、呼出元、package type、生成コードとtop-level awaitから分けて確認します。

AIものさし編集部

Node.jsのERR_REQUIRE_ESMを見たら、最初に実際のNode.jsのバージョン、requireしたファイル、読み込まれるpackageとバージョンを確認します。「ESMはrequireでは一切読めない」という古い説明だけでは、現行Nodeの動作を判断できません。ソースがimportでも、生成コードやtest runnerがrequireへ変換している場合があります。

以下は診断用の架空例です。実packageのinstall、Node.jsのバージョン切替、moduleの読込実験、build・test runnerの実行は行っていません。利用版の公式資料と、実行時のエラーを照合して手順を選びます。

エラーを出した実行環境を固定する

terminal、IDE、CI、test runnerが同じNodeを使うとは限りません。実行バージョンとpath、entrypoint、起動command、package manager、依存の解決済みバージョンを記録します。lockfileを消して再installする前に、直前の依存更新とruntime変更を確認します。

表は横にスクロールできます

記録分ける対象誤った判断
Node.jsのバージョンローカルとCIの実行版手元で通ったので全環境も通る
呼出元ファイルsourceか生成済みJSかimportを書いたのでrequireは存在しない
読込先package解決済みバージョン・exportspackage名だけでCJS/ESMを決める
エラーcodeREQUIRE_ESM・ASYNC_MODULE等すべて同じ置換で直す

stack traceで最初の呼出境界を探します。自分のコードが直接requireしているのか、CLI設定や依存packageが読んでいるのかで、変更する場所が違います。node_modulesを直接編集して通す方法を、再現可能な修正にはしません。

現行Nodeのrequire対応を確認する

Node.jsのErrors資料(新しいタブで開く)は、同期ES moduleのrequire対応に伴いERR_REQUIRE_ESMをdeprecatedとしています。履歴にはNode 20.19.0、22.12.0、23.0.0で同期ESM読込が既定になったことが記載されています。これは当該版の保守状態や、利用packageすべての互換性を保証するものではありません。

現在のCommonJS modules資料(新しいタブで開く)では、requireで読めるESMには同期的であることなどの条件があります。依存のmodule graphにtop-level awaitがある場合は、ERR_REQUIRE_ASYNC_MODULEなど別の条件を調べます。新しいNodeへ上げれば、非同期のmoduleまで同期requireできると考えないようにします。

表は横にスクロールできます

状態次の確認選択肢の方向
古いruntimeでESM依存をrequirepackageの対応版とNode要件対応runtimeまたは読込方式
現行runtimeで非同期ESMをrequiretop-level awaitを含む依存関係dynamic importなど非同期境界
sourceはimport、生成物はrequiretranspiler・test runner出力生成設定とruntimeの整合
loaderや設定ファイルで失敗そのtoolのmodule対応対象ファイルの形式を限定して変更

package typeと拡張子の範囲を読む

Packages資料(新しいタブで開く)で、.mjs、.cjs、.jsと近いpackage.jsonのtypeの関係を確認します。project rootのtypeだけで、依存packageや入れ子のpackage境界まで同じ形式と判断しません。

typeをmoduleへ変えると、その範囲の既存JSや設定ファイルが影響を受けます。require、module.exports、__dirname等を使うコードやtoolの設定を点検せず、rootの一行だけを変える修復にはしません。特定の設定ファイルだけCommonJSとして残すなら、そのtoolが対応する.cjsなどの方法を確認します。

表は横にスクロールできます

調整する範囲確認するもの避ける変更
app全体をESMへ寄せる起動・test・config・exportstypeだけ変更して終了
CJSからESM依存を読むdynamic importと非同期呼出元Promiseを同期値として使う
tool設定だけ残すtool対応の拡張子・loader全packageの形式を変更
依存バージョンを調整公式対応範囲と保守状況理由なく古い版へ固定

dynamic importを使うなら呼出元まで確認する

CommonJSからdynamic importを使う方法は選択肢ですが、importの結果は非同期です。呼出元がawaitできるか、初期化順序、失敗時の処理を確認します。同期APIの中へそのまま差し込める保証はありません。

また、module namespaceとdefault exportは同じではありません。目的の関数をどこから取得するかは、対象packageのexportsと利用APIに合わせます。すべてのmoduleへ機械的に.defaultを足す処理や、関数名を推測する例にはしません。

依存バージョンを戻す場合は、戻す理由、対応するAPI、保守・修正の状態、lock差分を確認します。動いた古い版を永久に安全な版と扱わず、暫定措置なら解除条件を残します。

sourceと実行される生成物を同じ条件で確かめる

TypeScriptなどを使う場合は、sourceのimport文だけでなく、実行対象のJSがどのmodule形式で出力されたかを確認します。dev サーバーでは変換されても、Nodeで直接起動する成果物は別の経路です。test runnerだけで失敗する場合も、その変換設定とNode版を分けます。

修正候補は一つずつにし、最小の読込、実際のentrypoint、必要なtestを対象環境で確認します。エラーcodeが変わっただけなら、新しい失敗段階を調べます。moduleが読めてもAPI呼出の戻り値や副作用まで期待どおりとは限りません。

変更を戻せる記録を残す

package.json、lock、runtime指定、tool設定のどれを変えたかを記録し、元の対応関係を保ちます。失敗時に依存一式を消すのではなく、今回の最小差分を戻して原因を切り分けます。

完成記録には、元のNode/package版、失敗境界、採用したmodule方式、確認した起動・test経路、未確認の環境を残します。runtime更新だけ、拡張子変更だけで全体が修復したと断定しないことが重要です。

確認した公式資料

質問から自分に合うAIを探す