こんにちは、Ryohei(@ityryohei)です!

JavaScriptのアプリケーションが大きくなるにつれて、コードを小さなファイルに分けて管理するモジュールシステムは必須の技術です。現代のWeb開発では、ECMAScriptの標準であるES Modules (ESM) がその役割を担います。

ESMの基本を学ぶことは、コードを整理し、Webサイトの表示速度を上げるための土台となります。

本記事では、ESMの基本的な使い方であるimportexportの構文から、ブラウザが裏側でモジュールを読み込む仕組み(非同期ロード)、そしてWebサイトの初期ロードを速くするテクニックを解説します。

コードが長くなりすぎてファイルが読みにくいけど、ファイルを分けたらどうやって繋げばいいんだろう?

上記の疑問にお答えします。

1. なぜコードを分割し、ES Modulesを使うのか

アプリ開発では、すべてのコードを一つの大きなファイル(main.jsなど)に書くのは非効率です。

コード分割のメリット

  1. 管理のしやすさ:
    関連する機能ごとにファイルを分けることで、コードが見やすく、修正しやすくなります。
  2. 名前の衝突防止:
    ファイルごとに変数の適用範囲(スコープ)が区切られるため、他のファイルで定義した変数名と間違えて使ってしまう「名前の衝突」を防げます。
  3. 効率化(Tree Shaking):
    使っていないコードを最終的なファイルから取り除く「Tree Shaking」という技術が可能になり、Webサイトの読み込みが速くなります。

ES Modules(ESM)は、このコード分割を実現するためのブラウザ標準の仕組みです。

2. ES Modulesの基本:exportとimportの使い方

ESMは、データをエクスポート(外に出す)する側と、それをインポート(取り込む)する側に分かれて機能します。

2-1. export:他のファイルで使えるようにする

他のファイルで利用したい関数や定数には、exportキーワードをつけます。

名前付きエクスポート (Named Exports)

複数の要素をエクスポートする標準的な方法です。

JavaScript(utils.js)

// 他のファイルで「PI」という名前で使えるようになる
export const PI = 3.14; 

// 他のファイルで「calculateArea」という名前で使えるようになる
export function calculateArea(radius) {
  return PI * radius * radius;
}

デフォルトエクスポート (Default Export)

ファイル内で最も主要な機能を一つだけエクスポートする場合に使います。

JavaScript(logger.js)

const logger = (message) => console.log(`[LOG]: ${message}`);

// このファイルで最も重要な関数としてエクスポート
export default logger; 

2-2. import:他のファイルから機能を取り込む

exportされた機能を取り込むには、importキーワードを使います。

名前付きインポート

exportされたときと同じ名前で、{}(波括弧)を使って受け取ります。

JavaScript(app.js)

// utils.js から PI と calculateArea を取り込む
import { PI, calculateArea } from './utils.js'; 

console.log(calculateArea(5)); 

注意点: ブラウザでESMを使う場合、ファイルパスの最後に拡張子(.jsなど)を省略することはできません。

デフォルトインポート

default exportでエクスポートされたものは、{}を使わず、自由に名前を付けて受け取ることができます。

JavaScript(app.js)

// logger.js から default export された関数を log という名前で受け取る
import log from './logger.js'; 

log('アプリケーション開始');

3. ブラウザがモジュールを読み込む仕組み

ESMがWebサイトの効率化に貢献できるのは、ブラウザでの読み込み方に秘密があります。

3-1. HTMLでの読み込み方

HTML内でESMを使うには、<script>タグに type="module" 属性をつけます。

HTML

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

type="module" をつけると、そのスクリプトは以下の特徴を持ちます。

  • 非同期ロード:
    他のHTMLの読み込みや解析をブロックせず、ファイルは非同期で読み込まれます。
  • 自動解決:
    main.jsが他のファイルをimportしていれば、ブラウザはそれらのファイルも自動で探し、並行して読み込みます。

3-2. ライブバインディング(参照の共有)

ESMは、インポートした値とエクスポート元の値を参照で繋げます(ライブバインディング)。

つまり、エクスポート元のファイルで値が変わると、インポートした側のファイルでも、その値がリアルタイムで更新されます。

JavaScript(counter.js)

// カウンターの値
export let count = 0; 
export const increment = () => { count++; };

JavaScript(app.js)

import { count, increment } from './counter.js';

console.log(count); // -> 0
increment(); // count の値を変更
console.log(count); // -> 1 (変更が反映されている)

4. Webサイトの初期ロードを速くするテクニック

ESMの応用として、Webサイトの初期ロードを速くする「遅延ロード」のテクニックを見てみましょう。

動的インポート(import())とは

通常のimport文はファイルの冒頭に書かれ、ページが読み込まれた瞬間に実行されます。これに対し、動的インポート (import()) は、コードの実行中必要なときだけモジュールを非同期で読み込むための仕組みです。

動的インポートは、モジュール全体をロードするPromiseを返します。

JavaScript

// 初期画面には不要な大きなファイルを、ボタンクリック時に読み込む例

document.getElementById('lazy-button').addEventListener('click', async () => {
  // 1. ファイルが非同期で読み込まれる(Promise)
  const module = await import('./large-chart-library.js'); 
  
  // 2. ロード完了後、モジュール内の関数を実行
  module.renderChart();
});

コード分割(Code Splitting)による効率化

この動的インポートを活用することで、Webサイトの初期ロードに必要なファイルサイズを大幅に減らせます。

  • 効果:
    アプリケーションの初期画面の表示に不要な大きなコードを別のファイルに分離(コード分割)し、ユーザーがその機能を使おうとしたときだけロードします。

これにより、ユーザーはより早くメイン画面を見ることができ、Webサイトの体感速度が向上します。

最後に

ES Modulesの基本を理解することは、モダンなWeb開発で必須のステップです。

  • export/import: コードを機能ごとに分割し、整理しましょう。
  • ライブバインディング: 値が参照で共有されることを意識してコードを書きましょう。
  • 動的インポート: 初期ロードに不要な大きなモジュールはimport()遅延ロードし、Webサイトを効率化しましょう。

これらの知識を活かし、分かりやすく、そして高速に動作するWebアプリケーションを構築してください。

以上、ES Modulesを活用したコード分割とWebサイト効率化の基本のご紹介でした!

この記事を書いた人

Ryohei

Webエンジニア / ブロガー

福岡のWeb制作会社に務めるWebエンジニアです。エンジニア歴は10年程で、好きな言語はPHPとJavaScriptです。本サイトは私がインプットしたWebに関する知識を整理し、共有することを目的に2015年から運営しています。Webに関するご相談があれば気軽にお問い合わせください。