LESSON 30分

「ユーザーがトップページを開くのに、管理画面のコードまでダウンロードさせる必要はない」と佐藤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 ShakingESM + sideEffects: false で不要コードを除去
Code Splittingルートベース + コンポーネントベースで分割
Dynamic Import条件付き・イベント駆動で必要時にロード
Bundle Analyzer定期的にバンドル構成を可視化し肥大化を防ぐ
ESM最適化manualChunks でキャッシュ効率を最大化

チェックリスト

  • Tree Shakingが効く条件を理解した
  • React.lazyでルートベースのコード分割ができる
  • Dynamic Importの活用パターンを知っている
  • バンドルサイズをCIで監視する方法を理解した

次のステップへ

バンドル最適化を学んだ。次は 画像・アセット最適化 で、メディアリソースの配信を最適化しよう。

推定読了時間: 30分