「script タグでファイルを読み込むのが辛くなってきた…」
「グローバル変数の管理が大変…」
「バンドラーって結局何をしてくれるの?」
前回は非同期処理の進化について学びました。今回は、コードの構造化と再利用性を高めるための重要な概念、「モジュール」について解説していきます。
jQueryからモジュールへ
まずは従来のjQuery時代のコード管理を振り返ってみましょう:
HTML
<!-- 従来のスクリプト読み込み -->
<script src="jquery.min.js"></script>
<script src="jquery-ui.min.js"></script>
<script src="my-utils.js"></script>
<script src="main.js"></script>
JS
// my-utils.js
var MyUtils = {
formatDate: function(date) {
return date.toISOString();
},
calculateTotal: function(items) {
return items.reduce(function(sum, item) {
return sum + item.price;
}, 0);
}
};
// main.js
$(document).ready(function() {
var total = MyUtils.calculateTotal(items);
$('#total').text(total);
});
このアプローチの問題点:
- 読み込み順序に注意が必要
- グローバルスコープの汚染
- 依存関係が不明確
- 名前空間の衝突リスク
モダンなモジュールシステム
JS
// utils.js
export const formatDate = (date) => {
return date.toISOString();
};
export const calculateTotal = (items) => {
return items.reduce((sum, item) => sum + item.price, 0);
};
// オプション:デフォルトエクスポート
export default {
formatDate,
calculateTotal
};
JS
// main.js
import { formatDate, calculateTotal } from './utils.js';
// または
import * as Utils from './utils.js';
// または
import DefaultUtils from './utils.js';
const total = calculateTotal(items);
document.querySelector('#total').textContent = total;
モジュールシステムの利点:
- スコープが隔離される
- 依存関係が明確
- コードの再利用が容易
- 名前空間の衝突を防止
import/exportの基本パターン
名前付きエクスポート/インポート
JS
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
// main.js
import { add, subtract } from './math.js';
// 別名をつける
import { add as addNumbers } from './math.js';
// すべてをオブジェクトとしてインポート
import * as MathUtils from './math.js';
ポイント:
- 複数の機能を個別にエクスポート可能
- 必要な機能だけをインポート可能
- 別名を付けて名前の衝突を回避
デフォルトエクスポート/インポート
JS
// user.js
export default class User {
constructor(name) {
this.name = name;
}
sayHello() {
return `Hello, ${this.name}!`;
}
}
// main.js
import User from './user.js';
// 任意の名前でインポート可能
import CustomUser from './user.js';
使い分けのポイント:
- モジュールの主要な機能を提供する場合に使用
- 1モジュールにつき1つのデフォルトエクスポートのみ可能
- インポート時に好きな名前を付けられる
モジュールの構造化パターン
機能ごとの分割
JS
// api/users.js
export const fetchUsers = async () => {
const response = await fetch('/api/users');
return response.json();
};
// api/posts.js
export const fetchPosts = async () => {
const response = await fetch('/api/posts');
return response.json();
};
// api/index.js
export * from './users.js';
export * from './posts.js';
// main.js
import { fetchUsers, fetchPosts } from './api';
ポイント:
- 関連する機能をファイルごとに分割
- インデックスファイルで再エクスポート
- 使用側は簡潔なインポートで済む
内部実装の隠蔽
JS
// database.js
// プライベート関数(エクスポートしない)
const initializeConnection = () => {
// ...
};
// パブリックAPI
export class Database {
constructor() {
this.connection = initializeConnection();
}
query(sql) {
// ...
}
}
カプセル化のポイント:
- exportしない関数は外部から参照不可
- 内部実装の詳細を隠蔽
- APIをクリーンに保つ
実践的なモジュール設計
設定の集中管理
JS
// config.js
export const CONFIG = {
API_URL: 'https://api.example.com',
TIMEOUT: 5000,
DEFAULT_LANGUAGE: 'ja'
};
// api.js
import { CONFIG } from './config.js';
export async function fetchData(endpoint) {
const response = await fetch(`${CONFIG.API_URL}${endpoint}`, {
timeout: CONFIG.TIMEOUT
});
return response.json();
}
ユーティリティ関数の整理
JS
// utils/date.js
export const formatDate = (date) => {
return new Date(date).toLocaleDateString();
};
// utils/number.js
export const formatCurrency = (amount) => {
return new Intl.NumberFormat('ja-JP', {
style: 'currency',
currency: 'JPY'
}).format(amount);
};
// utils/index.js
export * from './date.js';
export * from './number.js';
バンドラーとの連携
Webpackなどのバンドラーを使用する場合:
JS
// webpack.config.js
module.exports = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
}
};
バンドラーの役割:
- モジュールの依存関係を解決
- コードを単一ファイルにまとめる
- 最適化(圧縮、不要コードの削除など)
まとめ
モジュールシステムの導入により:
- コードの構造が明確に
- 依存関係の管理が容易に
- メンテナンス性が向上
- 再利用性が高まる
次回は「クラス構文で整理する」について解説し、オブジェクト指向プログラミングのモダンな実装方法を学んでいきましょう。