読み込み中...
読み込み中...
読み込み中...
読み込み中...
読み込み中...
モジュールとは、関連する機能をまとめた独立したコードの単位です。モジュールシステムが導入される前のJavaScriptでは、すべてのスクリプトが同じグローバルスコープを共有していたため、以下の問題がありました。
| 問題 | 説明 |
|---|---|
| 名前空間の汚染 | 異なるスクリプトで同名の変数が衝突 |
| 依存関係の管理 | スクリプトの読み込み順序を手動で管理 |
| 再利用性の低さ | 機能の分離・共有が困難 |
| テストの困難さ | グローバル状態に依存するため単体テストが難しい |
初期のJavaScriptでは <script> タグの読み込み順序に依存した開発が行われていました。IIFEや名前空間パターンで問題を緩和していましたが、根本的な解決にはなりませんでした。
モジュールシステムの発展は以下のように進みました。IIFE/名前空間パターン → CommonJS(Node.js、2009年)→ AMD(ブラウザ向け非同期ロード)→ ES Modules(言語標準、ES2015)。現在は ES Modules が標準ですが、Node.js のエコシステムでは CommonJS も広く使われています。
モジュールシステム以前のパターン
// モジュールシステムが無い時代のパターン
// 問題: グローバルスコープの汚染
// <script src="lib-a.js"></script> → var utils = { ... };
// <script src="lib-b.js"></script> → var utils = { ... }; // 衝突!
// 回避策1: 名前空間パターン
var MyApp = MyApp || {};
MyApp.utils = {
formatDate: function(date) {
return date.toLocaleDateString("ja-JP");
}
};
// 回避策2: IIFE + グローバル変数
var MyModule = (function() {
var privateVar = "秘密";
return {
getSecret: function() { return privateVar; }
};
})();
console.log(MyApp.utils.formatDate(new Date()));
console.log(MyModule.getSecret()); // "秘密"チャレンジ
名前空間パターンを使って、MathUtils オブジェクトに add(a, b) と multiply(a, b) メソッドを実装してください。グローバルスコープを汚染しないようにしましょう。
ポイント
モジュールシステムは名前空間の汚染、依存関係管理、再利用性の問題を解決するために進化してきました。IIFE → CommonJS → ES Modules と発展し、現在は ES Modules が言語標準です。
CommonJS は Node.js で採用されているモジュールシステムです。require() でモジュールを読み込み、module.exports でモジュールを公開します。
CommonJS の特徴は同期的な読み込みです。require() を呼ぶとファイルを読み込み、実行し、エクスポートされたオブジェクトを返します。サーバーサイドではファイルシステムからの読み込みが高速なため問題になりませんが、ブラウザではネットワーク遅延があるため不向きです。
| 構文 | 用途 |
|---|---|
require('module') | モジュールの読み込み |
module.exports = value | モジュール全体をエクスポート |
exports.name = value | 名前付きエクスポート(module.exports のショートカット) |
注意: exports は module.exports への参照です。exports = { ... } のように直接代入すると参照が切れてしまうため、module.exports = { ... } を使いましょう。
CommonJS の require / module.exports
// math.js — モジュールのエクスポート
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
// app.js — モジュールのインポート
const { add, subtract } = require("./math");
console.log(add(10, 5)); // 15
console.log(subtract(10, 5)); // 5
// デフォルトエクスポート的な使い方
// logger.js
class Logger {
log(msg) { console.log(`[LOG] ${msg}`); }
}
module.exports = Logger;
// app.js
const Logger = require("./logger");
const logger = new Logger();
logger.log("起動しました");CommonJS はモジュールをキャッシュします。同じモジュールを複数回 require() しても、最初の読み込み結果がキャッシュから返されます。これはシングルトンパターンのような動作になります。
キャッシュの確認: require.cache オブジェクトにキャッシュされたモジュールが格納されています。
チャレンジ
CommonJS 形式で calculator モジュールを作成してください。add、subtract、multiply の3つの関数を module.exports でエクスポートしてください。(※ この演習はブラウザ環境のため、module.exports の代わりにオブジェクトとして定義します)
ポイント
CommonJS は Node.js のモジュールシステムで、require() と module.exports を使います。同期的な読み込みとキャッシュが特徴です。exports を直接代入すると参照が切れるため module.exports を使いましょう。
**ES Modules(ESM)**はES2015で策定されたJavaScript言語標準のモジュールシステムです。import / export 構文を使い、静的な解析が可能な設計になっています。
| 構文 | 用途 | 例 |
|---|---|---|
export const x = 1 | 名前付きエクスポート | 複数エクスポート可能 |
export default value | デフォルトエクスポート | 1モジュールに1つだけ |
import { x } from './mod' | 名前付きインポート | 名前を指定して取り込む |
import mod from './mod' | デフォルトインポート | デフォルトエクスポートを取り込む |
import * as mod from './mod' | 名前空間インポート | 全エクスポートをオブジェクトとして取り込む |
import { x as y } | エイリアス | 別名でインポート |
ES Modules は静的です。import 文はファイルのトップレベルにのみ書けます(条件分岐の中には書けません)。この制約により、バンドラーがコードを静的に解析し、使われていないエクスポートを除去するTree Shakingが可能になります。
ES Modules の import / export
// utils.js — 名前付きエクスポート
export const PI = 3.14159;
export function circleArea(r) {
return PI * r * r;
}
export function circlePerimeter(r) {
return 2 * PI * r;
}
// Logger.js — デフォルトエクスポート
export default class Logger {
log(msg) {
console.log(`[${new Date().toISOString()}] ${msg}`);
}
}
// app.js — インポート
import Logger from "./Logger.js"; // デフォルト
import { circleArea, PI } from "./utils.js"; // 名前付き
import * as utils from "./utils.js"; // 名前空間
const logger = new Logger();
logger.log(`面積: ${circleArea(5)}`);
logger.log(`PI = ${utils.PI}`);| 特徴 | CommonJS | ES Modules |
|---|---|---|
| 構文 | require / module.exports | import / export |
| 読み込み | 同期的 | 非同期的(静的解析) |
| 評価 | 実行時 | コンパイル時 |
| Tree Shaking | 不可 | 可能 |
| ブラウザ | バンドラーが必要 | ネイティブ対応 |
| 拡張子 | .js(.cjs) | .mjs or "type": "module" |
チャレンジ
ES Modules の名前付きエクスポートとデフォルトエクスポートの知識を確認します。以下のモジュール定義に対して、正しいインポート文を完成させてください(※ ブラウザ環境のため、実際のモジュール構文ではなくオブジェクト形式で模倣します)。
ポイント
ES Modules は JavaScript の標準モジュールシステムで、import / export 構文を使います。静的解析が可能なため Tree Shaking によるバンドルサイズの最適化ができます。デフォルトエクスポートは1つ、名前付きエクスポートは複数持てます。
動的インポート import() は、モジュールを実行時に非同期で読み込む仕組みです。通常の import 文が静的(トップレベルでのみ使用可能)であるのに対し、import() は関数のように呼び出せ、条件分岐の中やイベントハンドラ内でも使えます。
import() は Promise を返すため、then や await と組み合わせて使います。主な用途は以下の通りです。
| 用途 | 説明 |
|---|---|
| コード分割(Code Splitting) | 初期読み込みサイズを削減 |
| 条件付き読み込み | 環境や条件に応じてモジュールを切り替え |
| 遅延読み込み(Lazy Loading) | 必要になったタイミングで読み込み |
| オンデマンドポリフィル | ブラウザの機能に応じてポリフィルを読み込み |
動的インポートの活用パターン
// 動的インポートの例
async function loadChart() {
// ユーザーがグラフ表示ボタンを押した時だけ読み込む
const { Chart } = await import("./chart-library.js");
const chart = new Chart("#canvas");
chart.render();
}
// 条件付きインポート
async function loadLocale(lang) {
const locale = await import(`./locales/${lang}.js`);
return locale.default;
}
// 複数モジュールの並列読み込み
async function loadPlugins() {
const [auth, analytics] = await Promise.all([
import("./plugins/auth.js"),
import("./plugins/analytics.js")
]);
auth.init();
analytics.track("page_view");
}良いモジュール設計は保守性と再利用性を高めます。
| プラクティス | 説明 |
|---|---|
| 単一責任 | 1モジュール = 1つの責務 |
| 明確なインターフェース | 公開するものを最小限に |
| バレルファイル(index.js) | 複数モジュールの再エクスポートで整理 |
| 循環参照の回避 | A → B → A のような相互依存を避ける |
| Tree Shaking 対応 | 名前付きエクスポートを使い、副作用を避ける |
バレルファイルは、ディレクトリ内の複数モジュールを index.js で再エクスポートするパターンです。インポートする側はディレクトリ名だけで必要なものを取り込めます。
バレルファイルによるモジュール整理
// バレルファイルのパターン
// utils/string.js
// export function capitalize(str) { ... }
// export function truncate(str, len) { ... }
// utils/array.js
// export function unique(arr) { ... }
// export function flatten(arr) { ... }
// utils/index.js(バレルファイル)
// export { capitalize, truncate } from "./string.js";
// export { unique, flatten } from "./array.js";
// 使う側: ディレクトリ名だけでインポート
// import { capitalize, unique } from "./utils";
// ブラウザ環境での模倣
const utils = {
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
},
truncate(str, len) {
return str.length > len ? str.slice(0, len) + "..." : str;
},
unique(arr) {
return [...new Set(arr)];
}
};
console.log(utils.capitalize("hello")); // "Hello"
console.log(utils.unique([1, 2, 2, 3])); // [1, 2, 3]チャレンジ
動的インポートを模倣する loadModule 関数を実装してください。モジュール名を受け取り、Promise で対応するモジュールオブジェクトを返します。modules マップからモジュールを検索し、見つからない場合は reject してください。
ポイント
動的インポート import() は実行時にモジュールを非同期で読み込む仕組みで、コード分割や遅延読み込みに活用されます。モジュール設計では単一責任、明確なインターフェース、バレルファイルによる整理、循環参照の回避が重要です。
実務での活用
プロジェクトが大きくなると、1つのファイルにすべてのコードを書くのは不可能になります。機能ごとにファイルを分けて import/export で接続するモジュール設計は、チーム開発の基盤です。「1ファイル=1責務」を意識して分割すると、どこに何があるか迷わないコードベースになります。
用語