定義と結論
バンドルとは、モジュールの依存関係を解析し、ブラウザへ配信するJavaScriptやCSSなどを適切な単位へまとめる処理です。 実行するツールをバンドラと呼びます。
「全部を一ファイルへ結合すること」だけがバンドルではありません。現代のバンドラは、必要なコードの選別、複数チャンクへの分割、最小化、アセット参照の解決、キャッシュ用ハッシュ付与まで担います。目標はファイル数を最小にすることではなく、正しく、速く、キャッシュしやすく配信することです。
なぜ必要なのか
開発者は責務ごとにモジュールを分け、import で再利用します。ブラウザもES Modulesを扱えますが、npmパッケージ、TypeScript、CSS import、画像、互換性、配信性能まで含めると、公開用に整理する工程が役立ちます。
import { formatDate } from "./date.js";
import { openModal } from "./modal.js";
import "./styles.css";
バンドラは入口から import をたどり、「何が必要で、いつ読み込むべきか」という依存グラフを出力ファイルへ変換します。
登場人物と対象
- エントリーポイント:
main.jsや各ページなど、探索の入口 - モジュール: import/exportでつながるソースやライブラリ
- 依存グラフ: どのモジュールが何に依存するかの関係
- バンドラ: Rollup、webpack、esbuildなど
- 開発ツール: Viteのように開発サーバーと本番ビルドを統合するもの
- チャンク: ビルド後に分割された配信単位
- ブラウザ: HTMLからチャンクを取得し、評価・実行する
Viteは開発時にはネイティブESMを活用し、本番ビルドではバンドルを行います。そのため「Vite=一つのバンドラ」と単純化せず、開発時と本番時の役割を分けて理解します。
処理の流れ

1. 入口を見つける
HTMLの <script type="module">、アプリの main.ts、ページ単位の入口などを設定から決めます。
2. importを解決する
相対パス、パッケージ名、別名を実ファイルへ解決し、依存を再帰的にたどります。
main.js
├─ app.js
│ ├─ user.js
│ └─ api.js
├─ style.css
└─ node_modules/library/index.js
3. 変換と解析を行う
構文を解析し、プラグインを通じてTypeScriptやCSSなどを処理します。静的な import は解析しやすい一方、実行時に組み立てたパスは制約を受けることがあります。
4. チャンクへ分ける
初期表示に必要なコード、複数ページで共有するコード、後で読むコードを分けます。import() は動的読込の境界として使われます。
const editor = await import("./editor.js");
editor.open();
5. 最適化して出力する
未使用コード削除、最小化、ハッシュ付与、ソースマップ生成などを設定に応じて行います。HTMLやCSSの参照先も生成名へ更新します。
一つにまとめる場合と分割する場合
| 方針 | 利点 | 注意点 |
|---|---|---|
| 単一バンドル | 構成が単純 | 初回に不要コードも読みやすい |
| ページ別 | 初回量を抑えやすい | 共有方法の調整が必要 |
| 機能別遅延読込 | 必要時だけ取得 | 操作時の待ち時間が生じる |
| 共有チャンク | 重複を減らせる | 更新時のキャッシュ境界に影響 |
HTTP/2・HTTP/3では複数リクエストの負担は以前より下がりましたが、ファイルを細かくすれば必ず速いわけではありません。各ファイルにはヘッダー、圧縮、解析、実行の費用があります。チャンク設計は「初回に必要か」「再利用されるか」「変更頻度は同じか」で判断します。
主要な最適化パターン
Tree shakingは、静的に利用されていないexportを出力から除く最適化です。副作用のあるモジュールや解析しにくい形式では期待どおり除去されないことがあります。
Code splittingは、コードを複数チャンクへ分けます。ページ、ルート、動的importが代表的な境界です。
Minificationは、変数名や空白などを縮めて転送量を減らします。圧縮とは別処理で、配信時にはBrotliやgzipも関係します。
Content hashingは、中身に応じた文字列をファイル名へ付けます。内容が変わらないチャンクは長くキャッシュし、変更したチャンクだけ新しいURLにできます。
Source mapは、生成コードと元コードの対応表です。本番デバッグに有用ですが、公開範囲やソース露出方針を確認します。
具体例:管理画面だけ重いエディター
一般利用者向けトップページと、管理者向け記事エディターが同じアプリにあるとします。エディターのライブラリが大きいのに、トップページの初期バンドルへ含めると、使わない利用者も取得・解析します。
管理画面のルートや操作時に動的importすれば、トップページから分離できます。ただし管理者がボタンを押してから初めて読み込むと待ち時間が出ます。管理画面へ入った時点で先読みするなど、利用の流れに合わせます。バンドルサイズ削減の目的は数字を小さくすることではなく、必要な時点まで不要な仕事を遅らせることです。
よくある誤解
「ファイルは少ないほど速い」
巨大な単一ファイルは、初回取得・解析・実行が重く、少しの変更でキャッシュ全体が無効になります。利用経路で考えます。
「Tree shakingですべての未使用コードが消える」
モジュール形式、副作用の宣言、import方法に左右されます。実際の出力を分析します。
「vendor は必ず分けるべき」
共有ライブラリの分離はキャッシュに効く場合がありますが、小規模アプリでは余計なリクエストや設定複雑化を招きます。測定して決めます。
「バンドルサイズと転送サイズは同じ」
未圧縮サイズ、gzip/Brotli後の転送サイズ、解析・実行コストは別です。圧縮しやすくても実行が重いコードがあります。
注意点とベストプラクティス
- まず既定の分割戦略を使い、根拠なく手動チャンクを増やさない
- 大きい依存は用途、代替、部分importを確認する
- 動的importには読み込み中・失敗時のUIを用意する
- チャンク名にハッシュを使い、HTMLは適切に再検証する
- 本番用ソースマップの公開方針を決める
- 重複パッケージや異なるバージョン混在を確認する
- サイズだけでなく実端末の解析・実行時間を測る
デバッグと確認方法
判断例: 分割が逆に遅くなる条件
商品一覧で日付整形、表、グラフを別々の動的importへ分けた結果、初期JavaScriptは小さくなっても、表示直後に三つのチャンクを待って操作可能時刻が遅れることがあります。これはサイズ削減だけを成功条件にした悪い例です。良い判断では、Networkの依存関係、各チャンクの取得開始時刻、解析・実行時間、キャッシュ再訪時の変化を比較します。
三機能が常に同時に必要なら同じチャンクへ戻す候補です。グラフだけが詳細画面で必要なら、その境界を維持し、画面遷移が予測できる時点で先読みします。変更後は低速回線と低性能端末で、初回表示、操作可能時刻、機能を開いた時の待ち時間を測ります。出力ファイル数ではなく利用経路の待ち時間が判断を決めます。
本番だけ動かない場合、まずDevToolsのNetworkでJS/CSSが200で取得できるか、MIME typeや公開パスが正しいかを見ます。Consoleの最初の例外も確認します。
- 本番ビルドを実行し、警告を読む
- 出力チャンクの数と大きさを確認する
- Networkで初期ロードと遅延ロードを区別する
- Coverageやバンドル分析で未使用量・大きな依存を探す
- キャッシュ無効時と再訪時の両方を測る
- 遅い端末・低速回線条件で操作する
- 動的import失敗時の表示を確認する
ファイル名が難読でも、元ソース、ソースマップ、ビルドログ、NetworkのInitiatorから追えます。生成物を直接修正するのではなく、入口と依存関係へ戻ります。
まとめ
バンドルは、依存グラフをブラウザへ配信できるチャンクへ変換する処理です。 単なる結合ではなく、分割、未使用コード削除、最小化、キャッシュ設計を含みます。最適なファイル数という固定解はありません。利用者がいつ何を必要とするかを考え、ビルド出力と実測で判断します。