「ユーザーがトップページを開くのに、管理画面のコードまでダウンロードさせる必要はない」と佐藤CTOは言った。「コード分割は、必要なコードを必要なタイミングで届ける技術だ。」
1. Tree Shaking
// BAD: ライブラリ全体をインポート
import _ from 'lodash'; // 全体(72KB gzip)
const result = _.get(obj, 'a.b');
// GOOD: 必要な関数だけインポート
import get from 'lodash/get'; // 関数単体(1KB gzip)
const result = get(obj, 'a.b');
// BEST: lodash-es を使う(ESM で Tree Shaking 可能)
import { get } from 'lodash-es';
Tree Shaking が効かないケース
// Side Effect のあるモジュールは Tree Shaking できない
// package.json で sideEffects フィールドを正しく設定する
{
"name": "my-library",
"sideEffects": false, // 全モジュールが副作用なし
// または特定のファイルだけ指定
"sideEffects": ["*.css", "./src/polyfills.ts"]
}
2. Code Splitting(コード分割)
// React.lazy + Suspense によるルートベースのコード分割
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const Products = lazy(() => import('./pages/Products'));
const Admin = lazy(() => import('./pages/Admin'));
const Checkout = lazy(() => import('./pages/Checkout'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<Products />} />
<Route path="/admin/*" element={<Admin />} />
<Route path="/checkout" element={<Checkout />} />
</Routes>
</Suspense>
);
}
// 動的インポートでコンポーネントレベルの分割
const HeavyChart = lazy(() => import(
/* webpackChunkName: "chart" */
/* webpackPrefetch: true */
'./components/HeavyChart'
));
3. Dynamic Import の活用
// 条件付きインポート
async function loadEditor() {
if (userRole === 'admin') {
const { RichEditor } = await import('./components/RichEditor');
return RichEditor;
}
const { SimpleEditor } = await import('./components/SimpleEditor');
return SimpleEditor;
}
// イベント駆動のインポート(ユーザー操作時に初めてロード)
button.addEventListener('click', async () => {
const { openModal } = await import('./components/Modal');
openModal();
});
4. Bundle Analyzer
# webpack の場合
npx webpack-bundle-analyzer dist/stats.json
# Vite の場合
npx vite-bundle-visualizer
バンドルサイズ監視の自動化
// .size-limit.json
[
{ "path": "dist/assets/index-*.js", "limit": "200 KB", "gzip": true },
{ "path": "dist/assets/vendor-*.js", "limit": "400 KB", "gzip": true },
{ "path": "dist/assets/*.css", "limit": "50 KB", "gzip": true }
]
5. ESM 最適化
// vite.config.ts - モダンブラウザ向け最適化
export default defineConfig({
build: {
target: 'es2020',
modulePreload: { polyfill: false },
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
},
},
},
});
まとめ
| トピック | 要点 |
|---|---|
| Tree Shaking | ESM + sideEffects: false で不要コードを除去 |
| Code Splitting | ルートベース + コンポーネントベースで分割 |
| Dynamic Import | 条件付き・イベント駆動で必要時にロード |
| Bundle Analyzer | 定期的にバンドル構成を可視化し肥大化を防ぐ |
| ESM最適化 | manualChunks でキャッシュ効率を最大化 |
チェックリスト
- Tree Shakingが効く条件を理解した
- React.lazyでルートベースのコード分割ができる
- Dynamic Importの活用パターンを知っている
- バンドルサイズをCIで監視する方法を理解した
次のステップへ
バンドル最適化を学んだ。次は 画像・アセット最適化 で、メディアリソースの配信を最適化しよう。
推定読了時間: 30分