モジュール分割設計とは何か(まずイメージから)
モジュール分割設計は、
「アプリ全体を、意味のある小さな“部品(ファイル)”に分けて、その関係をきれいに整理すること」 です。
1ファイル = 1モジュール、として
「このファイルは何を担当するのか?」
「どのファイルがどのファイルに依存してよいのか?」
を決めていく作業そのものが「モジュール設計」です。
ここが重要です。
最初から「正解の分割」を知っている人はいません。
大事なのは、
「1ファイルに全部書く」のをやめて、
「役割ごとに分けてみて、足りないところは後から調整する」
という感覚を持つことです。
まず決めるべきは「何ごとに 1 モジュールか」
機能ごとに分ける(feature 単位)
一番イメージしやすいのは「機能ごと」です。
例えば、簡単な ToDo アプリを作るなら:
タスクに関する処理
ユーザーに関する処理
API 通信
画面の初期化(エントリーポイント)
など、機能ごとに分けるイメージです。
todo/
todoModel.js ← タスクデータのクラスや操作
todoView.js ← 画面にタスクを表示する処理
todoController.js ← イベント処理(クリックされたらどうするか)
api.js ← サーバーとの通信
main.js ← アプリのエントリーポイント
「todoModel.js はタスクデータのことだけ考える」
「todoView.js は見た目のことだけ考える」
というように、1ファイルに「考えること」を絞るイメージです。
概念ごとに分ける(ドメイン単位)
少しだけ進んだ考え方として、「アプリの中の“名詞”」ごとに分ける方法もあります。
ユーザー(User)
商品(Product)
注文(Order)
といった「実世界の概念」がはっきりしているなら、
それぞれに1モジュールを割り当てるのも自然です。
models/
User.js
Product.js
Order.js
このとき、「User.js はユーザーという概念のことだけ考える」
「Order.js は注文という概念のことだけ考える」
というふうに、1モジュール=1概念にします。
ここが重要です。
「何ごとに 1 モジュールにするか」を一言で説明できると、分割の軸がブレにくくなります。
機能単位でも概念単位でも構いませんが、「自分のプロジェクトにとってしっくりくる粒度」を探していきます。
依存の向きをどう設計するか(上流と下流のイメージ)
「下にあるほど基礎」「上にあるほどアプリ寄り」
モジュール同士の関係は、矢印でイメージすると分かりやすいです。
例えば:
util(小さな共通関数)
↓
models(データ・ルール)
↓
services(アプリのビジネスロジック)
↓
ui / main(画面・エントリーポイント)
という「層」を作るイメージです。
画面は services に依存してよい、
services は models に依存してよい、
models は util に依存してよい。
でも、逆方向の依存はできるだけ避けるようにします。
(例えば、util が UI に依存する、など)
具体的な構成例を見てみます。
src/
util/
date.js
models/
Todo.js
services/
todoService.js
ui/
todoView.js
main.js
main.js はアプリ全体の入口として、他を組み合わせる役です。
循環しないように「片方向」に揃える
理想は、モジュール同士の矢印が「片方向だけ」に揃っている状態です。
例えば:
util/date.js ← models/Todo.js ← services/todoService.js ← ui/todoView.js ← main.js
このように、一方向に流れていれば循環参照も起きにくく、
「どっちからどっちを使ってよいか」が分かりやすくなります。
ここが重要です。
「どの層がどの層に依存してよいか」を自分なりに一度決めると、
後からモジュールを追加するときに迷いが減り、循環参照の事故も起こりにくくなります。
1 モジュールは「何を export して、何を隠すか」
モジュールの「外向きの顔」を決める
各モジュールには、「外に見せる API」と「中で完結させるロジック」があります。
例えば、タスクモデルのモジュールなら:
// todoModel.js
let nextId = 1; // モジュールスコープ内の状態(外から隠す)
export class Todo {
constructor(title) {
this.id = nextId++;
this.title = title;
this.done = false;
}
toggle() {
this.done = !this.done;
}
}
export function createTodo(title) { // 外に見せたい窓口
return new Todo(title);
}
JavaScriptnextId のような内部だけで使う状態は export せず、Todo クラスや createTodo のように「使ってもらいたいもの」だけを export します。
他のモジュールは、Todo と createTodo を知っていれば十分で、nextId の存在を気にしなくて済みます。
ここが重要です。
モジュール分割は、「ファイルを分けること」以上に
「外から見せたいインターフェース(export)と、中の実装(非 export)を分けること」 が本質です。
index.js を使って「窓口」を作る
関連するモジュールが増えてきたら、index.js で再エクスポートして入口を整理する方法もあります。
models/
Todo.js
User.js
index.js
// models/Todo.js
export class Todo { /* ... */ }
JavaScript// models/User.js
export class User { /* ... */ }
JavaScript// models/index.js
export { Todo } from "./Todo.js";
export { User } from "./User.js";
JavaScriptこれで、他のファイルからは
import { Todo, User } from "./models/index.js";
JavaScriptのように使えます。
「models の中身がどう分割されているか」は外から意識しなくてよくなり、
フォルダの中を整理し直しても、index.js さえ合わせて直せば外の import はそのままにできます。
小さな例で「モジュール設計の流れ」を通してみる
例:シンプルな ToDo アプリを段階的に分割する
最初にありがちなのは、全部 main.js に書くパターンです。
// main.js(何も分割していない状態)
const todos = [];
function addTodo(title) { /* 追加 */ }
function toggleTodo(id) { /* 完了切り替え */ }
function render() { /* DOM 更新 */ }
document.getElementById("addBtn").addEventListener("click", () => {
const title = document.getElementById("titleInput").value;
addTodo(title);
render();
});
render();
JavaScriptここから少しずつ分割していきます。
段階1:データ操作を別モジュールへ
// todoStore.js
const todos = [];
export function addTodo(title) {
todos.push({ id: Date.now(), title, done: false });
}
export function toggleTodo(id) {
const todo = todos.find((t) => t.id === id);
if (todo) todo.done = !todo.done;
}
export function getTodos() {
return todos;
}
JavaScript// main.js
import { addTodo, toggleTodo, getTodos } from "./todoStore.js";
function render() {
const list = document.getElementById("list");
list.innerHTML = "";
for (const todo of getTodos()) {
const li = document.createElement("li");
li.textContent = todo.title + (todo.done ? "(完了)" : "");
li.addEventListener("click", () => {
toggleTodo(todo.id);
render();
});
list.appendChild(li);
}
}
// イベント設定や画面のことだけ考える
document.getElementById("addBtn").addEventListener("click", () => {
const title = document.getElementById("titleInput").value;
addTodo(title);
render();
});
render();
JavaScriptこの段階で、
todoStore.js は「タスクの配列を管理する役」main.js は「DOM やイベントを扱う役」
と役割が分かれました。
段階2:さらに view の責任を分離する
// todoView.js
export function renderTodos(todos, { onToggle }) {
const list = document.getElementById("list");
list.innerHTML = "";
for (const todo of todos) {
const li = document.createElement("li");
li.textContent = todo.title + (todo.done ? "(完了)" : "");
li.addEventListener("click", () => onToggle(todo.id));
list.appendChild(li);
}
}
JavaScript// main.js
import { addTodo, toggleTodo, getTodos } from "./todoStore.js";
import { renderTodos } from "./todoView.js";
function render() {
renderTodos(getTodos(), {
onToggle(id) {
toggleTodo(id);
render();
},
});
}
document.getElementById("addBtn").addEventListener("click", () => {
const title = document.getElementById("titleInput").value;
addTodo(title);
render();
});
render();
JavaScriptここまで来ると:
todoStore.js は「データ」todoView.js は「表示」main.js は「全体の流れ(イベントと render の呼び出し)」
という風に、それぞれのモジュールの「仕事」がかなりハッキリしてきます。
ここが重要です。
最初から完璧に分割しようとしなくていいので、
「全部1ファイル」→「データと画面を分ける」→「画面の描画をさらに切り出す」
のように、少しずつ段階を踏んでモジュールを育てていくのが現実的です。
モジュール設計で意識しておきたいチェックポイント
「このファイルは一言で何を担当しているか?」と言えるか
モジュール分割がうまくいっているかどうかは、
各ファイルについて「このファイルは一言で言うと何をしている?」と自問してみると分かります。
「ユーザーデータの定義と、その操作をまとめたモジュール」
「API との通信だけを担当するモジュール」
「画面のレンダリングだけを担当するモジュール」
一言でスッと言えるなら、そのモジュールはだいたい良い形になっています。
逆に、「あれもこれもやってて説明しにくい…」と感じるなら、
中身を2つ以上のモジュールに分けるサインです。
import の方向がごちゃごちゃしていないか
モジュール同士の import が、
上から下へ ばかりなのかあっちこっちから、あっちこっちへ 行っていないか
を軽く意識してみてください。
同じフォルダ内で互いを import しあっているファイルが多いときは、
「共通の土台モジュールを作る」「さらに上の階層に組み合わせ役を作る」チャンスです。
名前付き export と default export の使い分け
モジュールの「主役」がはっきりしているなら default export、
複数の機能をまとめて提供したいなら名前付き export、
という風に使い分けると、モジュールの意図が読みやすくなります。
例えば:
Todo.js なら export default class TodotodoStore.js なら export function addTodo / export function getTodos のように名前付き
といった感じです。
まとめ
ES6 モジュールの「モジュール分割設計」の核心は、
「ファイルを意味のある単位で分け、依存の向きと export を通して“誰が何を知っていてよいか”をデザインすること」 です。
ポイントを整理すると:
何ごとに 1 モジュールか(機能単位・概念単位など)をまず決める
モジュール同士の依存が一方向に流れるように「層」を意識する
1 モジュールの中で「外に見せる export」と「中だけで使う実装」を分ける
関連モジュールが増えたら index.js で再エクスポートして窓口をまとめる
最初から完璧は目指さず、「1ファイル → ざっくり分割 → さらに整理」と段階的に進める
練習としては、今 1 ファイルにまとまっているコードを
「データ」「画面」「通信」の 3 つぐらいに分けてみることから始めるとちょうど良いです。
そのとき、「このファイルは一言でいうと何?」を必ず自分に問いかけてみてください。
それを繰り返していくと、モジュールの“形”を見る目がどんどん育っていきます。
