Dev Study
← 解説「バンドルの中身を調べる — 何が重いかを可視化する」に戻る

サンプルコードで身につける: バンドルの中身を調べる — 何が重いかを可視化する

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1ng build の出力サイズを読む

ビルドのたびに表示されるサイズ一覧の読み方です。Initial totalが初期表示でダウンロードされる合計、Lazy chunkが遅延読み込み分です。この表を毎回眺める習慣だけで、肥大化への感度が大きく上がります。

ng build

# ── 出力例と読み方 ──
# Initial chunk files | Names       | Raw size  | Estimated transfer size
# main-A1B2C3.js      | main        | 420.10 kB | 105.20 kB
# polyfills-D4E5.js   | polyfills   |  34.50 kB |  11.30 kB
# styles-F6G7.css     | styles      |  12.20 kB |   3.10 kB
#                     | Initial total | 466.80 kB | 119.60 kB
#
# Lazy chunk files    | Names       | Raw size  |
# chunk-H8I9.js       | admin-page  | 180.40 kB |
#
# 読み方:
#   ・Initial total が「初期表示の重さ」。まずここを削る
#   ・Estimated transfer size は圧縮後の転送量(実際に近い数字)
#   ・Lazy chunk に分かれていれば遅延読み込みが効いている証拠
#   ・前回のビルドと比べて急に増えた行があれば要調査

2source-map-explorer で内訳を面積図にする

バンドルの中身をライブラリ単位で可視化する手順です。面積図では大きいものほど広く表示されるため、削減候補が一目で決まります。調査のときだけソースマップ付きでビルドし、配布物には含めないのが運用上の注意点です。

# 1. ソースマップ付きで本番ビルドする(調査のときだけ)
ng build --source-map

# 2. メインバンドルを可視化する
npx source-map-explorer dist/my-app/browser/main-*.js

# => ブラウザに面積図が開く。読み方:
#    ・面積 = バンドル内で占めるサイズ。大きい箱から疑う
#    ・node_modules 配下の箱がライブラリ。
#      「chart.js だけで 210kB」のような事実がここで判明する
#    ・自分のコード(src)が大きい場合は分割・遅延を検討

# 3. 対処の選択肢は3つ。図を見てから選ぶ
#    a. 遅延させる(@defer / loadComponent の後ろに追いやる)
#    b. 部分 import に変える(ライブラリの一部だけ使う)
#    c. 軽い代替に置き換える・自前実装する

# 注意: ソースマップは本番公開物に含めない(ソース流出につながる)

3lodash を丸ごと入れない — 部分 import

巨大ライブラリの全部読み込みを部分importに直す定番例です。lodashを丸ごとimportすると1関数のために全体がバンドルに入ります。lodash-esから関数単位で取り込めば、ツリーシェイキングで使う分だけが残ります。

// ── 遅い書き方: ライブラリ全体がバンドルに入る ──
// import _ from 'lodash';
// const grouped = _.groupBy(orders, 'status');
// => groupBy しか使っていないのに lodash 全体(約70kB)が入る

// ── 速い書き方: ES Modules 版から関数単位で import ──
import { groupBy } from 'lodash-es';

type Order = { id: number; status: string };

export function summarize(orders: Order[]) {
  // 使うのは groupBy だけ。ツリーシェイキングで残りは削られる
  return groupBy(orders, 'status');
}

// 効果は source-map-explorer で確認する。
// lodash の箱が小さくなっていれば成功。
// なお標準の Object.groupBy で足りるならライブラリ自体を外せる

4重い日付ライブラリを標準機能で置き換える

「表示フォーマットだけのために重いライブラリを抱えていた」をブラウザ標準のIntlで置き換える例です。ライブラリを消せばその分は確実にゼロになります。置き換え前に、本当にその機能しか使っていないかの確認が必須です。

// ── 遅い書き方: 日付の表示整形だけに moment(約70kB)を使う ──
// import moment from 'moment';
// const label = moment(date).format('YYYY年M月D日');

// ── 速い書き方: ブラウザ標準の Intl なら追加サイズゼロ ──
const formatter = new Intl.DateTimeFormat('ja-JP', {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
});

export function toLabel(date: Date): string {
  return formatter.format(date); // => 「2026年8月6日」
}

// Angular 標準の DatePipe({{ date | date: 'longDate' }})も
// 内部で同様の仕組みを使うため、まずはパイプで足りないか確認する。
// タイムゾーン計算など複雑な要件が出てから専用ライブラリを検討する

5重いライブラリを @defer の裏に隠す

消せない重いライブラリを初期バンドルから追い出す構成例です。描画ライブラリへの依存を1つの子コンポーネントに閉じ込め、それを@deferで包むと、ライブラリごと遅延チャンクに移動します。ライブラリ依存を1か所に集めるのが成功条件です。

import { Component } from '@angular/core';
import { HeavyChartComponent } from './heavy-chart.component';

// heavy-chart.component.ts の側だけが chart.js を import する:
//   import { Chart } from 'chart.js';  // 重い依存はこの1ファイルに集約

@Component({
  selector: 'app-report',
  imports: [HeavyChartComponent],
  // @defer で包むと、chart.js ごと遅延チャンクに分割される
  template:
    '<h1>月次レポート</h1>' +
    '@defer (on viewport) {' +
    '  <app-heavy-chart />' +
    '} @placeholder {' +
    '  <p>グラフはスクロールすると表示されます</p>' +
    '}',
})
export class ReportComponent {}

// 注意: 親コンポーネントなど @defer の外で chart.js を import して
// いると分割されない。source-map-explorer で main から
// chart.js が消えたことを確認して初めて完了