JavaScriptのモジュールシステム:CommonJSとESMが共存する理由
JavaScriptの世界では、CommonJS(CJS)とECMAScript Modules(ESM)という2つの主要なモジュールシステムが並存している。なぜ1つに統一されないのか、なぜ両方が必要なのか、その理由と背景について解説する。
モジュールシステムとは
モジュールシステムとは、コードを機能ごとに分割し、再利用可能な形で管理するための仕組みである。適切にモジュール化されたコードは以下のメリットがある:
- コードの再利用性が高まる
- 依存関係が明確になる
- 名前空間の衝突を避けられる
- テストが容易になる
JavaScriptモジュールシステムの歴史的発展
JavaScriptのモジュールシステムは長い進化の過程を経てきた。以下はその重要な節目である:
CommonJSの誕生と特徴
誕生背景
CommonJSは2009年頃に登場した。当時のJavaScriptには標準のモジュールシステムがなく、特にサーバーサイドでのコード分割と管理に課題があった。Node.jsの登場とともにCommonJSが広く採用され、サーバーサイドJavaScriptの基盤となった。
主な特徴
// モジュールのエクスポート
const myFunction = () => {
console.log('Hello from CommonJS');
};
module.exports = {
myFunction
};
// モジュールのインポート
const { myFunction } = require('./myModule');
CommonJSの特徴:
-
require()
によるモジュールの読み込み -
module.exports
またはexports
によるエクスポート - 同期的な読み込み方式(順番にロードする)
- Node.jsのデフォルトモジュールシステムとして普及
- ブラウザでの直接利用には変換ツール(Browserify、webpack等)が必要
ESModulesの登場
誕生背景
ECMAScript Modules(ESM)は、ECMAScript 2015(ES6)で標準化されたJavaScriptの公式モジュールシステムである。JavaScriptの言語仕様としてモジュールを標準化することで、ブラウザとNode.jsの両方で一貫して使えるモジュールシステムを目指した。
主な特徴
// モジュールのエクスポート
export const myFunction = () => {
console.log('Hello from ESM');
};
// デフォルトエクスポート
export default function() {
console.log('Default export');
}
// モジュールのインポート
import { myFunction } from './myModule.js';
import defaultFunction from './myModule.js';
ESModulesの特徴:
-
import
とexport
による静的インポート/エクスポート - 非同期的な読み込みが可能(動的インポート)
- ツリーシェイキングによる最適化が容易
- トップレベルの
await
をサポート - 静的解析が可能(コード実行前に依存関係を解決できる)
CommonJSとESMの構造比較
以下の図はCommonJSとESMの基本的な構造の違いを示している:
なぜ2つのモジュールシステムが共存しているのか
1. 歴史的経緯
CommonJSはESMよりも先に登場し、Node.jsのエコシステムに深く根付いた。膨大な数のnpmパッケージがCommonJSで書かれており、それらを一斉にESMに移行することは現実的ではない。
2. 互換性の問題
CommonJSとESMには根本的な違いがある:
- CommonJSは動的かつ同期的
- ESMは基本的に静的かつ非同期的
この違いにより、両者は完全な互換性を持たない。例えば、CommonJSでは条件分岐によって異なるモジュールを読み込むことができるが、ESMの静的インポートではそれができない。
3. 異なるユースケース
両システムには、それぞれ適したユースケースがある:
-
CommonJS:
- サーバーサイドでの同期的な処理
- レガシーなNode.jsアプリケーション
- 条件付きモジュール読み込みが必要な場合
-
ESM:
- モダンなブラウザ環境
- ツリーシェイキングによる最適化が重要な場合
- 静的解析が有効なケース
- トップレベルawaitを使いたい場合
主な違い(一目でわかる比較表)
特徴 | CommonJS | ESModules |
---|---|---|
構文 |
require() , module.exports
|
import /export
|
読み込みタイミング | 同期的 | 非同期的(静的宣言は事前解析) |
トップレベルawait | 非サポート | サポート ✅ |
条件付き読み込み | サポート ✅ | 静的インポートでは不可(動的インポートなら可能) |
ファイル拡張子 | 省略可能 ✅ | 通常は必須 |
Node.jsでのデフォルト | Node.js 12まで | Node.js 16以降で完全サポート |
循環依存の扱い | 部分的な評価結果を返すことがある | より厳格で予測可能 ✅ |
読み込み時の性能 | 実行時に依存関係を解決 | 事前に静的解析可能で最適化しやすい ✅ |
ブラウザでの直接利用 | 不可(ツールが必要) | 可能 ✅ |
実際の動作イメージ
CommonJS(実行時に読み込み)
ESM(事前解析してから実行)
現状と今後の展望
現在、JavaScriptエコシステムは徐々にESMへの移行が進んでいる:
- 多くの新しいツールやライブラリはESMを優先または両方をサポート
- Node.jsはESMをフルサポート(ただし
.mjs
拡張子の使用やpackage.json
の"type": "module"
設定が必要な場合も) - モダンブラウザはESMを直接サポート
- TypeScriptなどのツールもESMをサポート
一方で:
- 巨大なCommonJSのエコシステムはすぐには置き換わらない
- 両方のシステムが共存する期間はまだ長く続く見込み
- 相互運用のためのツールやパターンが重要になる
実践的なアドバイス
プロジェクトでの選択基準
新規プロジェクトでは:
- モダンなブラウザ/Node.js環境が前提ならESMを選択
- 広い互換性や既存のエコシステムとの統合が重要ならCommonJSも考慮
- デュアルパッケージ(両方をサポート)の提供も検討
両方のシステムを扱う際のポイント
// package.jsonでのESM設定
{
"type": "module",
"exports": {
"import": "./index.js",
"require": "./index.cjs"
}
}
-
.mjs
と.cjs
の拡張子で明示的に区別する - ビルドツール(webpack, Rollup等)を使って変換する
- 動的インポートを活用する
デュアルパッケージの実装例
以下は両方のモジュールシステムに対応したパッケージの構造例である:
my-package/
├── dist/
│ ├── index.js # ESM形式のコード
│ └── index.cjs # CommonJS形式のコード
├── src/
│ └── index.js # ソースコード(通常ESM)
├── package.json # 以下の設定を含む
└── README.md
// package.jsonの設定例
{
"name": "my-package",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"exports": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"scripts": {
"build": "rollup -c" // RollupなどでESM/CJSの両方をビルド
}
}
まとめ
CommonJSとESMは、それぞれの歴史的背景と技術的特性により、当面は共存し続けるだろう。JavaScriptの長期的な方向性としてはESMが標準になっていくと考えられるが、膨大なCommonJSのエコシステムがある限り、両方のシステムを理解し、適切に扱えることが重要である。
エコシステム全体のESMへの移行は進んでいるが、それは緩やかなプロセスであり、過渡期である現在は両方のシステムの違いと相互運用性を理解することがJavaScript開発者にとって重要なスキルとなっている。
以下の図は、JavaScriptエコシステムの移行状況を表している:
最終的には、ESMが主流となることが予想されるが、CommonJSが完全になくなることはないだろう。エコシステムの健全な進化のためには、移行期における互換性と相互運用性の確保が重要である。
参考文献
- Node.js Documentation - ECMAScript Modules
- MDN Web Docs - JavaScript modules
- ES modules: A cartoon deep-dive by Lin Clark
- CommonJS vs. ES Modules: A Beginner's Guide
- 2ality Blog - ES6 Modules by Dr. Axel Rauschmayer
- State of JavaScript 2022 - 開発者調査データ
- How to Create a Hybrid NPM Module - webpackドキュメント