Node.js 24でrequire(esm)が安定化 — NestJS 12が純粋なESMに移行できた理由

Maru

@maru

Node.js 24 require(esm) 안정화 — NestJS 12의 순수 ESM 전환이 가능했던 이유

Node.js 24でrequire(esm)が安定化 — NestJS 12が純粋なESMに移行できた理由

JavaScriptバックエンドエコシステムで長年の悩みの種だったERR_REQUIRE_ESMエラーがついに過去のものになろうとしています。Node.js 24において、CommonJS環境からESMパッケージを同期的に直接呼び出せるrequire(esm)機能が安定化したためです。おかげで、複雑なバンドラー設定やデュアルビルドのような回避策なしに、最新のESMパッケージを自然に導入できる道が開かれました。バックエンドのモジュール統合を加速させるこの変化の核心と、実務上の意義をまとめます。

Node.js 24のrequire(esm)の動作原理と主な制約

これまでJavaScript開発者を苦しめてきたERR_REQUIRE_ESMエラーが、Node.js 24でついに完全に解決されました。主要コントリビューターであるJoyee Cheung氏の主導で完成したこのメカニズムは、CommonJS環境からESMパッケージを直接同期的にロードできるようにサポートします。

動作原理は、V8エンジンレベルでESMモジュールグラフを同期的にパース・分析する点にあります。本来、ECMAScript仕様上、トップレベルawaitを含まないESMグラフは同期的な評価が可能です。Node.jsランタイムはこの点を活用し、モジュールを同期的にコンパイルした後、結果を即座に返します。

おかげで、既存のCommonJSプロジェクトでもビルドツールチェーンを複雑に修正することなく、純粋なESMライブラリをそのまま呼び出すことができます。

javascript
// app.cjs (Node.js 24+ 환경)
const { someEsmFunction } = require('esm-only-package');

// 별도의 비동기 래핑 없이 즉시 동기 실행 가능
const result = someEsmFunction();
console.log(result);

ただし、このメカニズムには、読み込もうとするESMモジュールグラフの内部にトップレベルawaitが含まれてはならないという明確な制約があります。トップレベルawaitが含まれると評価プロセスが非同期化されPromiseが返されるため、同期関数であるrequireで呼び出すとERR_REQUIRE_ASYNC_MODULEエラーが発生します。

幸いなことに、ほとんどのnpmライブラリは下位互換性のためにトップレベルawaitを使用していないため、実務レベルでは制約を気にすることなく組み合わせて利用できます。

NestJS 12のESM移行が苦痛ではない理由

NestJS 12ロードマップの最大の変更点は、すべてのコアパッケージを純粋なESMに移行することです。かつてであれば、この決定は既存のCommonJSベースのアプリケーションの下位互換性を破壊する致命的な問題を引き起こしたでしょう。しかし、Node.js 24が提供する同期的なrequire(esm)のおかげで、既存プロジェクトのコードを全く修正することなく、最新の純粋なESMパッケージを読み込んで使用できるようになりました。

InfoQのロードマップレポートやbyteiotaの分析によると、NestJSの創設者であるKamil Myśliwiec氏は、Node.jsによるrequire(esm)サポートが「ESM移行を実質的に可能にした最後のピース」であり、この機能がなければ今回の移行は大きな意味をなさなかっただろうと強調しました。互換性の壁がランタイムレベルで解消されたことで、フレームワークのコアチームが安心して全面的な体質改善に着手できるようになったのです。

おかげでNestJS 12は、既存のビジネスロジックに影響を与えることなく、長年のコンパイラの負債とモジュール設定の複雑さを完全に解消する模範的な事例を示しました。開発者はプロジェクトの構造をすぐに変更しなくてもESM専用ライブラリを自由に組み合わせられるようになり、モジュールシステム間の長年の葛藤からついに解放されました。

デュアルパッケージの障壁の終焉とビルドパイプラインの簡素化

これまでオープンソースライブラリのメンテナーは、CommonJSとESMの両方の環境をサポートするために、2つのフォーマットのビルド成果物を同時に提供してきました。この過程で、アプリケーションが同一パッケージを異なるモジュールシステムとして個別に読み込むことで発生する「デュアルパッケージハザード(Dual Package Hazard)」が深刻な問題となっていました。メモリ上でシングルトンオブジェクトが重複生成されることでグローバル状態が壊れたり、データベース接続が二重に確立されたりするなど、追跡が極めて困難なランタイムバグが頻発していました。

Node.js 24の require(esm) 安定化は、この問題を解決する重要な鍵です。ライブラリを純粋なESMフォーマット一つだけで配布しても、CommonJSバックエンドがこれを直接同期的に呼び出せるためです。単一のモジュールインスタンスとして評価・ロードされるため、シングルトンオブジェクトの重複生成によるバグが根本的に遮断されます。

結果として、複雑だったビルドパイプラインと tsconfig.json の設定が劇的に簡素化されます。メンテナーはバンドラーでの面倒な条件付きエクスポート設定を維持し、CJSとESMバージョンを二重コンパイルしていた無駄を終わらせることができます。エンタープライズ開発チームも、ハイブリッドビルドやバンドリングのストレスに悩まされることなく、純粋なESMライブラリをプロダクションに自由に導入できるようになりました。

2026年バックエンド移行のための実務チェックリスト

純粋なESMバックエンドに安全に移行するためには、まずランタイム環境を require(esm) 機能が安定化したNode.js 24、またはバックポートが完了したNode.js 22.12.0以降、20.19.0以降のLTSバージョンに移行する必要があります。

次に、TypeScript設定ファイルの modulemoduleResolution オプションを NodeNext に変更し、すべてのローカルファイルインポートパスの末尾に .js 拡張子を明記するルールを適用します。ESM環境では使用できない __dirname__filename 変数は、import.meta.url を活用して代替するようにコードを修正します。

複雑な二重ビルド設定を完全に取り除き、コンパイラツールチェーンを簡素化できる今こそ、バックエンドのモジュールアーキテクチャをモダン化する絶好の機会です。


参考リンク