ストーリー
高橋アーキテクトがバンドル分析ツールの出力を見せた。
Tree-shaking
使用されていないコードをバンドルから自動的に除去する最適化です。
// utils.ts - 多数のユーティリティ関数
export function formatDate(date: Date): string { /* ... */ }
export function formatCurrency(amount: number): string { /* ... */ }
export function formatPhone(phone: string): string { /* ... */ }
export function formatAddress(addr: Address): string { /* ... */ }
// page.ts - formatDate だけ使用
import { formatDate } from './utils';
// Tree-shaking により、formatCurrency, formatPhone, formatAddress はバンドルに含まれない
Tree-shakingを阻害するパターン
// NG: 名前空間インポート → 全関数がバンドルに含まれる
import * as utils from './utils';
utils.formatDate(new Date());
// NG: CommonJS モジュール(require)→ 静的解析不可
const utils = require('./utils');
// OK: 名前付きインポート → 使用関数のみバンドル
import { formatDate } from './utils';
// OK: ES Modules → 静的解析可能
export { formatDate } from './utils';
Code-splitting(コード分割)
バンドルを複数のチャンクに分割し、必要なタイミングで読み込みます。
// Before: 全てを1つのバンドルに
import { Dashboard } from './pages/Dashboard';
import { Settings } from './pages/Settings';
import { Analytics } from './pages/Analytics';
import { AdminPanel } from './pages/AdminPanel'; // 管理者のみが使う
// After: ルートベースのCode-splitting(React例)
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Analytics = lazy(() => import('./pages/Analytics'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/analytics" element={<Analytics />} />
<Route path="/admin" element={<AdminPanel />} />
</Routes>
</Suspense>
);
}
分割戦略
| 戦略 | 説明 | 効果 |
|---|---|---|
| ルートベース | ページごとに分割 | 初期ロードサイズを大幅削減 |
| コンポーネントベース | 重いUIコンポーネントを分割 | モーダル、チャートなど |
| ベンダー分割 | サードパーティライブラリを分離 | キャッシュ効率の向上 |
Lazy Loading(遅延読み込み)
表示領域に入った時点で初めてリソースを読み込みます。
// 画像の遅延読み込み
// <img loading="lazy" src="product.jpg" alt="商品画像">
// Intersection Observer を使った遅延読み込み
class LazyLoader {
private observer: IntersectionObserver;
constructor() {
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadElement(entry.target as HTMLElement);
this.observer.unobserve(entry.target);
}
});
},
{ rootMargin: '200px' } // 200px手前から読み込み開始
);
}
observe(element: HTMLElement): void {
this.observer.observe(element);
}
private loadElement(element: HTMLElement): void {
const src = element.dataset.src;
if (src) {
(element as HTMLImageElement).src = src;
}
}
}
// コンポーネントの遅延読み込み(React例)
const HeavyChart = lazy(() => import('./components/HeavyChart'));
function AnalyticsPage() {
const [showChart, setShowChart] = useState(false);
return (
<div>
<h1>分析</h1>
<button onClick={() => setShowChart(true)}>チャートを表示</button>
{showChart && (
<Suspense fallback={<p>チャートを読み込み中...</p>}>
<HeavyChart />
</Suspense>
)}
</div>
);
}
バンドル分析
// webpack-bundle-analyzer でバンドルの中身を可視化
// npm install --save-dev webpack-bundle-analyzer
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
}),
],
};
// 確認すべきポイント
const analysisChecklist = [
'重複しているライブラリはないか',
'使っていないライブラリが含まれていないか',
'巨大なライブラリをより軽量な代替に置き換えられないか',
'ルートごとに適切に分割されているか',
];
// 軽量代替の例
const alternatives = {
'moment.js (300KB)': 'date-fns (25KB) or dayjs (7KB)',
'lodash (70KB)': 'lodash-es (Tree-shakable) or 個別インポート',
'chart.js (200KB)': 'lightweight-charts (40KB)',
};
まとめ
| ポイント | 内容 |
|---|---|
| Tree-shaking | 未使用コードの自動除去。ES Modulesが前提 |
| Code-splitting | バンドルを複数チャンクに分割。ルートベースが基本 |
| Lazy Loading | 必要になった時点で初めて読み込む |
| バンドル分析 | webpack-bundle-analyzerで中身を可視化 |
| 目標 | 初期バンドルを200KB以下に抑える |
チェックリスト
- Tree-shakingの仕組みと阻害パターンを理解した
- Code-splittingの3つの分割戦略を把握した
- Lazy Loadingの実装方法を理解した
- バンドル分析ツールの使い方を知った
次のステップへ
次は「画像とアセット最適化」を学びます。Webサイトの転送量の大半を占める画像の最適化テクニックです。
推定読了時間: 30分