基礎

JavaScriptのモジュール入門|import・exportでコードを分割・再利用する方法

JavaScriptのモジュールは、コードをファイル単位で分割し、必要な部分だけを他のファイルから取り込む仕組みです。ES6で標準化された import / export 構文を使うことで、コードの再利用性、保守性、名前の衝突防止が実現できます。

この記事では、名前付きエクスポートとデフォルトエクスポートの違い、インポートの書き方、実践的なモジュール設計パターンを解説します。

基本的な使い方

モジュールでは、export で外部に公開する関数や変数を指定し、import で他のファイルから取り込みます。

JavaScript(math.js)
// 名前付きエクスポート(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 };
JavaScript(main.js)
// 名前付きインポート(必要なものだけ取り込む)
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つだけメインとなるものをエクスポートする方法です。インポート時に波括弧が不要で、任意の名前で受け取れます。

JavaScript(User.js)
// デフォルトエクスポート(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;
}
JavaScript(app.js)
// デフォルトインポート(波括弧なし、任意の名前で受け取れる)
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

JavaScript
// インポート時にリネーム(名前の衝突を避ける)
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')); // 文字列の結合
JavaScript(index.js - re-export)
// 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でモジュールを使う

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の構築後に実行されます。

実践例:モジュール設計パターン

JavaScript(api.js)
// 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"> を指定し、ローカルサーバー上で動作させる