本文へスキップ
JavaScript 応用 JavaScript ES Modules import

JavaScriptのコードをモジュールに分割しよう

更新日:2026-08-02

処理が増えたら、データ取得、DOM生成、画面制御などの役割ごとにファイルを分けます。

前回まで一つのscript.jsに書いていた記事取得機能を、API通信、DOM生成、全体制御の三つへ分割します。分割後も動作が変わらないことが今回の目標です。

今回使用するファイル構成

javascript-practice/
├── index.html
└── js/
    ├── api.js
    ├── render.js
    └── script.js

モジュールを作成して読み込む

関数をexportする

api.jsから関数を公開します。

export async function fetchArticles() {
  const response = await fetch('/api/articles');

  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }

  return response.json();
}

importして利用する

script.jsで読み込みます。

import { fetchArticles } from './api.js';

const articles = await fetchArticles();
console.log(articles);

相対パスには拡張子まで記述します。

HTMLからmoduleとして読み込む

<script type="module" src="js/script.js"></script>

モジュールは自動的に遅延実行され、変数はファイル外のグローバル領域へ直接追加されません。ローカルファイルを直接開くと読み込み制限が起きる場合があるため、Live ServerなどのHTTPサーバーで確認します。

モジュール分割の考え方

分割しすぎない

一度しか使わない数行の処理まで分けると、読むファイルが増えます。「何を担当するファイルか」を説明できる単位で分割します。

記事一覧を三つの役割へ分ける

api.jsで通信だけを担当する

export async function fetchArticles() {
  const response = await fetch('data/articles.json');

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  const articles = await response.json();

  if (!Array.isArray(articles)) {
    throw new TypeError('記事データが配列ではありません');
  }

  return articles;
}

このファイルはHTML要素を知りません。データ取得に成功すれば配列を返し、失敗すれば例外を投げます。

render.jsでDOM生成を担当する

export function renderArticles(container, articles) {
  container.replaceChildren();

  articles.forEach((article) => {
    const item = document.createElement('li');
    item.textContent = article.title;
    container.append(item);
  });
}

export function showStatus(element, message) {
  element.textContent = message;
}

操作対象を引数で受け取るため、特定のidへ強く依存しません。

script.jsで処理を組み合わせる

import { fetchArticles } from './api.js';
import { renderArticles, showStatus } from './render.js';

const loadButton = document.querySelector('#load-button');
const status = document.querySelector('#status');
const articleList = document.querySelector('#article-list');

async function loadArticles() {
  loadButton.disabled = true;
  showStatus(status, '読み込み中です。');

  try {
    const articles = await fetchArticles();
    renderArticles(articleList, articles);
    showStatus(status, `${articles.length}件を読み込みました。`);
  } catch (error) {
    console.error(error);
    showStatus(status, '記事を取得できませんでした。');
  } finally {
    loadButton.disabled = false;
  }
}

loadButton.addEventListener('click', loadArticles);

script.jsは、どの要素とどの機能を接続するかを担当します。それぞれのファイルの役割を一文で説明できれば、分割の意図が伝わりやすくなります。

exportとimportの種類を理解する

今回使ったのは名前付きexportです。一つのファイルから複数の値を公開でき、import側では同じ名前を波括弧で指定します。

export function firstFunction() {}
export function secondFunction() {}
import { firstFunction, secondFunction } from './functions.js';

一つだけを代表として公開するdefault exportもありますが、名前を自由に変えられるため、初めは対応関係が明確な名前付きexportを基本にすると読みやすくなります。

よくあるエラー

  • Cannot use import statement outside a moduletype="module"を確認する
  • Failed to resolve module specifier./と拡張子.jsを確認する
  • CORS関連のエラー:ファイルを直接開かずHTTPサーバーを使う
  • exportが見つからない:export側とimport側の名前・大文字小文字を確認する

まとめ

exportで公開し、importで利用します。次の記事では日付と時刻を扱います。