← 解説「バンドルの中身を調べる — 何が重いかを可視化する」に戻る
サンプルコードで身につける: バンドルの中身を調べる — 何が重いかを可視化する
解説で学んだ概念を、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 が消えたことを確認して初めて完了