概要
モジュールとは「処理の塊」ではない。
それは責務の単位であり、他との依存関係をコントロールする構造上の境界線である。
本稿では、JavaScriptにおけるモジュール設計を以下の視点から体系化する:
- ESModules vs CommonJS の根本的違い
- export/import構文の設計ルール
- 分割と責務の境界定義
- 依存関係の明示的設計
- ルートエントリと内部構造の整理
1. ESModules vs CommonJS:構文と動作の差異
特性 | ESModules (ESM) | CommonJS (CJS) |
---|---|---|
構文 |
import / export
|
require() / module.exports
|
実行タイミング | 静的(パース時に解決) | 動的(実行時に解決) |
拡張子 |
.mjs / .js (type:module) |
.cjs / .js (type:commonjs) |
top-level await | ✅ 可能 | ❌ 不可 |
✅ ESMが主流だが、Node.jsとの互換性ではCJSが未だ根強い
→ プロジェクト開始時点でESM / CJSどちらを使うかを明示すべき
2. モジュールの構成原則:意味単位での分割
project/
├─ modules/
│ ├─ user/
│ │ ├─ index.ts
│ │ ├─ service.ts
│ │ └─ validator.ts
│ ├─ auth/
│ │ ├─ index.ts
│ │ └─ session.ts
- ✅ モジュールは機能(feature)単位で分割
- ❌ utils や common のような横断的な名目だけでまとめない
3. export戦略:構造を公開するか抽象化するか
// user/index.ts
export { getUser, updateUser } from './service';
export { validateUser } from './validator';
- ✅
index.ts
で公開APIを整理することで外部依存をコントロール - ✅ 内部構造を隠蔽し、意図されたエントリポイントのみ公開
4. 依存関係の設計と制御
- ✅ モジュール → サブモジュールの一方向依存を維持
- ❌ 循環依存(A→B→A)は構造崩壊の原因
- ✅ 共通依存(例:logger / config)はDI(依存注入) or Adapter層に隔離する
// NG
userService → config → userService
// OK
userService → configAdapter → config
5. ESM特有の import/export パターン
// 名前付きエクスポート
export function hello() {}
// デフォルトエクスポート
export default function () {}
// インポート側
import { hello } from './module';
import main from './module';
→ ✅ 名前付きで意図を明示、defaultは1モジュール = 1主要機能がある場合のみ使用
6. top-level await と構造化非同期
// ESMのみで使える構文
const data = await fetchData();
- ✅ 非同期初期化ロジックをmainエントリで直書き可能
- ✅ setup用モジュールとして
.bootstrap.ts
のような命名を採用する例も多い
設計判断フロー
① 実行環境はNode.jsかブラウザか? → ESM/CJSの選択
② 機能単位で責務が切れているか? → modules/ を構造で分割
③ エクスポートする構造は抽象化されているか? → index.tsの活用
④ 依存関係に循環はないか? → 一方向 & Adapter層を設計
⑤ 非同期初期化はどこに書く? → top-level await または明示的setup関数に分離
よくあるミスと対策
❌ util.js に全処理をまとめて import で吸い上げ
→ ✅ 意味単位のモジュールに分割し、単一責務の原則を適用
❌ デフォルトエクスポートが多用され、関数の意図が読めない
→ ✅ 名前付きエクスポートで意図を構文で明示
❌ 内部構造まで全部exportし、外部モジュールからの依存が崩壊
→ ✅ index.ts
で公開するインターフェースを制限・統制する
結語
モジュールとは、再利用ではなく、“意味の境界線を設計する”ための構造である。
- 責務ごとに構造を切り
- エクスポートは意図を明示し
- 依存を一方向に制御する
JavaScriptにおけるモジュール設計とは、
「プロジェクトの破綻を防ぐための、構造と言語による契約」である。