ES Modules と CommonJS の違い

ES Modules(ESM)と CommonJS(CJS)は、JavaScript の2つの主要なモジュールシステムです。両者には重要な違いがあり、プロジェクトに応じて適切に使い分ける必要があります。

構文の違い

// ES Modules
import { add } from './math.js';
export const PI = 3.14159;
export default function main() {}
// CommonJS
const { add } = require('./math');
exports.PI = 3.14159;
module.exports = function main() {};

主要な違いの比較

特徴ES ModulesCommonJS
読み込み静的(コンパイル時)動的(実行時)
非同期可能同期のみ
トップレベル await可能不可
ブラウザ対応ネイティブ不可

静的 vs 動的

ES Modules(静的)

import/export はファイルの先頭で静的に解析される。条件分岐内では使用不可。これにより Tree Shaking が可能。

CommonJS(動的)

require() は実行時に評価される。条件分岐内で使用可能。静的解析が難しい。

// ES Modules - これはエラー
if (condition) {
  import { func } from './module.js'; // SyntaxError
}

// CommonJS - これは OK
if (condition) {
  const { func } = require('./module');
}

this の違い

// ES Modules のトップレベル
console.log(this); // undefined

// CommonJS のトップレベル
console.log(this); // module.exports を指す

ファイル拡張子

// ES Modules(ブラウザ)- 拡張子必須
import { func } from './utils.js';

// CommonJS - 拡張子省略可
const { func } = require('./utils');

エクスポートの違い

ES Modules のエクスポート

エクスポートは読み取り専用のバインディング。元のモジュールで値が変わると、インポート側でも反映される。

CommonJS のエクスポート

エクスポートは値のコピー。プリミティブ値は元の変更が反映されない。

// ES Modules - バインディング
// counter.js
export let count = 0;
export function increment() { count++; }

// main.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1(反映される)
// CommonJS - コピー
// counter.js
let count = 0;
module.exports = {
  count,
  increment: () => { count++; }
};

// main.js
const counter = require('./counter');
console.log(counter.count); // 0
counter.increment();
console.log(counter.count); // 0(反映されない)

循環参照の扱い

両方のシステムで循環参照は可能ですが、挙動が異なります。

ES Modules:未初期化のバインディングにアクセスするとエラー
CommonJS:その時点での exports のスナップショットを取得

相互運用性

Node.js では ESM から CJS を、CJS から ESM を読み込むことができます(一部制限あり)。

// ES Modules から CommonJS を読み込む
import cjsModule from './cjs-module.cjs';

// CommonJS から ES Modules を読み込む(非同期)
const esmModule = await import('./esm-module.mjs');

どちらを使うべきか

ES Modules を選ぶ場合

新規プロジェクト、ブラウザで動作させたい、Tree Shaking を活用したい、トップレベル await が必要

CommonJS を選ぶ場合

既存の Node.js プロジェクト、ESM 未対応のライブラリに依存、動的な require が必要

現在のトレンドとしては、ES Modules への移行が進んでいます。新規プロジェクトでは ES Modules を選択することが推奨されます。