Node.js モジュールは『ESM 時代』への過渡期
長年 CommonJS が主流だった Node.js もエコシステム全体が ESM へ移行中で、両者の理解は必須です。本記事では編集部の視点で、現状と移行を公開情報をもとに整理します。Node.js 本番運用 もご参考に。
主要な違い
(1) ESM:import/export、静的解析、tree shaking。(2) CommonJS:require/module.exports、動的、Node 標準。(3) 同期 vs 非同期。(4) top-level await:ESM のみ。(5) __dirname/__filename:ESM では不要。
package.json の "type" 設定
(1) "type": "module":ESM デフォルト。(2) "type": "commonjs":CJS デフォルト(未指定時)。(3) .mjs:ESM 強制。(4) .cjs:CJS 強制。(5) 影響範囲:そのpackage 内のみ。
相互運用
(1) ESM から CJS:import OK。(2) CJS から ESM:dynamic import 必要。(3) named export:CJS の default のみ。(4) esbuild/SWC:自動変換。(5) tsx/ts-node:TypeScript 含む。
dual package の実装
(1) exports field:エントリポイント設定。(2) "types": "./dist/index.d.ts"。(3) "import": "./dist/index.mjs"。(4) "require": "./dist/index.cjs"。(5) tsup/vitestでdual ビルド。TypeScript 上級 もご参考に。
主要パッケージの状況
(1) chalk/node-fetch:ESM 専用化(公開情報をもとに)。(2) express:CJS 中心。(3) vitest/Hono:ESM First。(4) tsx:両対応実行ツール。(5) Bun/Deno:ESM ネイティブ。
移行の現実
(1) 新規プロジェクト:ESM 強く推奨。(2) レガシー:CJS のまま OK。(3) 段階的移行:dynamic import で対応。(4) require 不可:ESM では使えず注意。(5) Jest → Vitest:ESM対応で移行容易。Bun-Deno 実践 もご参考に。
失敗しがちなパターン
(1) ESM 化したら関連パッケージ動かない。(2) __dirname 使えなくなる。(3) 動的 require 困難。(4) Jest 設定複雑。(5) dual package hazard:両方同時 import で問題。対策は、(1)互換性事前確認、(2)import.meta.url、(3)dynamic import、(4)Vitest 移行、(5)1つに統一、です。