JavaScriptのモジュールは、コードをファイル単位で分割し、必要な部分だけを他のファイルから取り込む仕組みです。ES6で標準化された import / export 構文を使うことで、コードの再利用性、保守性、名前の衝突防止が実現できます。
この記事では、名前付きエクスポートとデフォルトエクスポートの違い、インポートの書き方、実践的なモジュール設計パターンを解説します。
基本的な使い方
モジュールでは、export で外部に公開する関数や変数を指定し、import で他のファイルから取り込みます。
// 名前付きエクスポート(named export)
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// まとめてエクスポートすることも可能
function subtract(a, b) {
return a - b;
}
function divide(a, b) {
if (b === 0) throw new Error('0で割ることはできません');
return a / b;
}
export { subtract, divide };// 名前付きインポート(必要なものだけ取り込む)
import { add, multiply, PI } from './math.js';
console.log('PI:', PI);
console.log('3 + 5 =', add(3, 5));
console.log('4 * 6 =', multiply(4, 6));
// すべてをまとめてインポート(名前空間として使う)
import * as MathUtils from './math.js';
console.log('10 - 3 =', MathUtils.subtract(10, 3));
console.log('20 / 4 =', MathUtils.divide(20, 4));PI: 3.14159
3 + 5 = 8
4 * 6 = 24
10 - 3 = 7
20 / 4 = 5名前付きインポートは波括弧 { } で囲み、エクスポート時の名前と一致する必要があります。import * as Name を使うと、モジュール内のすべてのエクスポートをオブジェクトとしてまとめてインポートできます。
デフォルトエクスポート
デフォルトエクスポートは、モジュールから1つだけメインとなるものをエクスポートする方法です。インポート時に波括弧が不要で、任意の名前で受け取れます。
// デフォルトエクスポート(1ファイルに1つだけ)
export default class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
greet() {
return 'こんにちは、' + this.name + 'です';
}
toString() {
return this.name + ' <' + this.email + '>';
}
}
// デフォルトと名前付きを混在させることも可能
export function createAdmin(name) {
const user = new User(name, name + '@admin.com');
user.role = 'admin';
return user;
}// デフォルトインポート(波括弧なし、任意の名前で受け取れる)
import User from './User.js';
// デフォルトと名前付きを同時にインポート
import UserClass, { createAdmin } from './User.js';
const user = new User('田中太郎', 'tanaka@example.com');
console.log(user.greet());
console.log(user.toString());
const admin = createAdmin('佐藤');
console.log(admin.role);
console.log(admin.toString());こんにちは、田中太郎です
田中太郎
admin
佐藤 <佐藤@admin.com> デフォルトエクスポートは、クラスや主要な関数を1つだけエクスポートする場合に使います。Reactコンポーネントはこのパターンが一般的です。一方、ユーティリティ関数の集まりなどは名前付きエクスポートが適しています。
リネームとre-export
// インポート時にリネーム(名前の衝突を避ける)
import { add as mathAdd } from './math.js';
import { add as stringAdd } from './string-utils.js';
console.log(mathAdd(1, 2)); // 数値の加算
console.log(stringAdd('a', 'b')); // 文字列の結合// re-export: 複数モジュールを1つの入口にまとめる
export { add, subtract } from './math.js';
export { formatDate, parseDate } from './date-utils.js';
export { default as User } from './User.js';
// 利用側はindex.jsからまとめてインポートできる
// import { add, formatDate, User } from './utils/index.js';re-exportパターン(バレルファイル)は、ライブラリやユーティリティモジュールのエントリポイントとしてよく使われます。利用側は個別のファイルパスを知らなくても、まとめてインポートできます。
HTMLでモジュールを使う
<!-- type="module" を指定する -->
<script type="module" src="./main.js"></script>
<!-- インラインでも使える -->
<script type="module">
import { add } from './math.js';
console.log(add(1, 2));
</script>ブラウザでモジュールを使うには、script タグに type="module" を指定します。モジュールスクリプトは自動的にstrictモードで実行され、defer と同じようにDOMの構築後に実行されます。
実践例:モジュール設計パターン
// API通信モジュール
const BASE_URL = 'https://api.example.com';
async function request(endpoint, options = {}) {
const url = BASE_URL + endpoint;
const response = await fetch(url, {
headers: { 'Content-Type': 'application/json' },
...options
});
if (!response.ok) throw new Error('API Error: ' + response.status);
return response.json();
}
export const userAPI = {
getAll: () => request('/users'),
getById: (id) => request('/users/' + id),
create: (data) => request('/users', {
method: 'POST',
body: JSON.stringify(data)
}),
update: (id, data) => request('/users/' + id, {
method: 'PUT',
body: JSON.stringify(data)
})
};API通信をモジュールに分離することで、コンポーネントやページから直接 fetch を呼ばずに済み、URLの変更やエラーハンドリングの修正を1箇所で行えます。
import() 関数を使うと、必要なタイミングでモジュールを動的に読み込めます。const module = await import('./heavy-module.js') のように書き、初期読み込み時間の短縮(コード分割)に活用できます。
モジュール(type="module")は、セキュリティ上の理由からローカルファイル(file://)では動作しません。開発時はローカルサーバー(npx serve や VS Code の Live Server など)を使う必要があります。
まとめ
exportで公開、importで取り込み。名前付きエクスポートは波括弧{ }でインポートする- デフォルトエクスポートは1ファイル1つで、任意の名前でインポートできる
import * as Nameで全エクスポートをまとめて取り込める- re-exportパターンで複数モジュールを1つの入口にまとめられる
- HTMLでは
<script type="module">を指定し、ローカルサーバー上で動作させる