ビルド最適化
Elftia のメインバンドルは 1,812 KB から 759 KB(-58%)に最適化されています。このドキュメントでは、後退を防ぐための最適化戦略と標準を記録します。
バンドルサイズの制限
ハード制限(必ず遵守)
| メトリクス | 制限 | 現在 | 確認方法 |
|---|---|---|---|
| 最大単一チャンク | < 1 MB | 759 KB | npm run verify:build |
| メインバンドル(gzip 圧縮後) | < 300 KB | 221 KB | npm run verify:build |
| Vite 警告 | 0 | 0 | ビルド出力 |
推奨目標
| メトリクス | 目標 | 現在 | 優先度 |
|---|---|---|---|
| メインバンドル(非圧縮) | < 500 KB | 759 KB | P1 |
| 総バンドル | < 3 MB | 6.24 MB | P2 |
| 初回画面読み込み | < 2s | ~2s | P0 |
確認コマンド
# Quick verification (4 key metrics)
npm run verify:build
# Detailed analysis (chunk categorization stats)
npm run analyze:build
# Visual analysis (generate stats.html)
npm run build:renderer
# Open dist/stats.html
コード分割戦略
ルートレベルのコード分割(必須)
すべてのページコンポーネントは、遅延読み込みのために React.lazy を使用する必要があります。lazy/ は用途別に整理されたディレクトリです(pages.tsx / workspaces.tsx / inline.tsx / shell.tsx / skeletons.tsx / with-suspense.tsx / index.ts バレル)。新しいページを追加する場合は、lazy/pages.tsx を変更してください。
// packages/renderer/src/app/lazy/pages.tsx
export const LazySettingsPage = lazy(() => import('../Settings'));
export const SuspenseSettings = withSuspense(LazySettingsPage, PageSkeleton);
// packages/renderer/src/app/App.tsx — old import paths still valid (barrel re-export)
import { SuspenseSettings as Settings } from './components/lazy';
<Route path="/settings" element={<Settings />} />
// Don't do this: direct import of page components
import Settings from './components/Settings';
<Route path="/settings" element={<Settings />} />
大型コンポーネントの遅延読み込み(推奨)
単一コンポーネントが 100 KB を超える場合、または大規模なサードパーティライブラリに依存する場合は、遅延読み込みを使用します。
すでに遅延読み込みされているコンポーネント:
| コンポーネント | サイズ | 依存関係 |
|---|---|---|
MermaidDiagram | 451 KB | mermaid |
HtmlPreviewPanel | 198 KB | iframe sandbox |
CodeEditor | 34 KB | CodeMirror |
Shell / StandaloneShell | 9 KB | xterm |
判断基準:
| 条件 | 遅延読み込みするか |
|---|---|
| コンポーネントバンドル > 100 KB | はい |
| 大規模なサードパーティライブラリに依存 | はい |
| 初回画面に不要 | はい |
| 使用頻度が低い | はい |
| 小さな共通コンポーネント(< 10 KB) | いいえ |
| 初回画面に必要なコアコンポーネント | いいえ |
| 頻繁に切り替えられる UI コンポーネント | いいえ |
Context の最適化
グローバルに必要でない Context はページレベルのコンポーネントへ移動します。
// Good: page-level Context
export function Settings() {
return (
<SettingsProvider>
<SettingsContent />
</SettingsProvider>
);
}
// Bad: load unnecessary Context globally
<GlobalContext>
<Routes />
</GlobalContext>
警告
Context を移動する前に依存関係を必ず分析し、ページをまたいだ状態共有が壊れないようにしてください。
ベンダーチャンク設定
現在の戦略では、更新頻度と使用頻度に基づいてグループ化しています。
// vite.config.js — manualChunks
{
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
'vendor-ui': ['@radix-ui/react-context-menu', '@radix-ui/react-dialog', ...],
'vendor-icons': ['lucide-react'],
'vendor-utils': ['clsx', 'tailwind-merge', 'class-variance-authority', 'zustand'],
'vendor-markdown': ['react-markdown', 'remark-gfm', 'rehype-highlight'],
'vendor-codemirror': ['@codemirror/state', '@codemirror/view', ...],
'vendor-xterm': ['@xterm/xterm', '@xterm/addon-fit', '@xterm/addon-web-links'],
}
グループ化の原則
| 種類 | 更新頻度 | キャッシュ優先度 | 例 |
|---|---|---|---|
| コアフレームワーク | 低 | 最高 | React, React DOM |
| 大規模なサードパーティライブラリ | 低 | 高 | CodeMirror, xterm |
| UI コンポーネントライブラリ | 中 | 中 | Radix UI, lucide |
| ユーティリティライブラリ | 中 | 中 | clsx, zustand |
新しい依存関係の確認
新しいサードパーティ依存関係を追加する場合:
# 1. Check size
npm info <package> dist.unpackedSize
# 2. If > 100 KB, add to vendor chunks
# 3. If high update frequency, create separate vendor chunk
# 4. Verify tree shaking support
# 5. Run npm run analyze:build to check impact
Tree Shaking
正しいインポート方法
// Good: named imports (supports tree shaking)
import { Button, Input, Select } from '@/components/ui';
import { Home, Settings, User } from 'lucide-react';
// Good: Radix UI namespace imports (official recommendation)
import * as Dialog from '@radix-ui/react-dialog';
// Bad: import entire library
import * as UI from '@/components/ui';
import * as Icons from 'lucide-react';
未使用インポートの確認
npm run typecheck # TypeScript detects unused imports
npm run lint:eslint # ESLint warns about unused variables
Vite 本番ビルド設定
// vite.config.js — key configuration
build: {
minify: 'terser', // terser has better compression than esbuild
sourcemap: false, // disable sourcemap in production
chunkSizeWarningLimit: 500, // chunk size warning threshold (KB)
terserOptions: {
compress: {
drop_console: true, // remove console.log (keep warn/error)
drop_debugger: true, // remove debugger
},
},
}
よくある問題と解決策
メインバンドルが大きすぎる(> 500 KB)
調査手順:
npm run build:rendererを実行し、dist/stats.htmlを開く- メインバンドルの構成を確認し、最も大きなモジュールを見つける
- 大型コンポーネントに遅延読み込みが不足していないか確認する
- 不要なグローバルインポートがないか確認する
解決策:
- 大型コンポーネントを遅延読み込みに変換する
- 未使用インポートを削除する
- ページコンポーネントをルートレベルの遅延読み込みに変換する
ベンダーチャンクが大きすぎる(> 500 KB)
解決策:
- 大型ベンダーをより小さなチャンクに分割する
- 更新頻度に基づいて再グループ化する
- 重複する依存関係を確認する
総バンドルが大きすぎる(> 5 MB)
解決策:
- チャートライブラリ(Mermaid、Cytoscape など)を遅延読み込みする
- 使用頻度の低い機能や依存関係を削除する
- より小さな代替ライブラリの使用を検討する
コード分割に失敗した
調査手順:
lazy/ディレクトリ内の関連する分割ファイルで遅延読み込み設定を確認する(ページレベルはlazy/pages.tsx、ワークスペース本体はlazy/workspaces.tsx、インラインチャットコンポーネントはlazy/inline.tsx)- 遅延読み込みされたコンポーネントが
App.tsxで使用されているか確認する - Vite 設定の
manualChunksを確認する
コミット前チェックリスト
新しいページを追加する場合
- ページコンポーネントを
lazy/pages.tsx(または対応する分割ファイル)に追加した -
withSuspenseでラップし、fallback を指定した -
App.tsxで遅延読み込み版を使用している -
npm run build:rendererを実行して個別チャンクを確認した
大型コンポーネントを追加する場合
- コンポーネントサイズ > 100 KB → 遅延読み込みを使用する
- 大規模なサードパーティライブラリに依存 → 遅延読み込みを使用する
- 適切なローディング状態を提供する
サードパーティ依存関係を追加する場合
- 依存関係のサイズを確認する
- 依存関係 > 100 KB → ベンダーチャンクに追加する
- 更新頻度が高い → 個別のベンダーチャンクにする
- Tree Shaking をサポート → 名前付きインポートを使用する
-
npm run analyze:buildを実行して影響を確認する
継続的な最適化の提案
各リリース前
npm run verify:build
npm run build:renderer | grep -i "warning"
# If main bundle increased > 50 KB, investigate reason
毎月
npm run build:renderer
# Open dist/stats.html to check optimizable modules
npm outdated
npm update
四半期ごと
- すべてのベンダーチャンク設定を見直す
- 新しい最適化戦略を評価する
- 使用頻度の低い機能を削除するかどうかを評価する
- この標準ドキュメントを更新する
CI/CD 統合
CI にバンドルサイズチェックを追加することを推奨します。
- name: Build and verify
run: |
npm run build:renderer
npm run verify:build || echo "Warning: Bundle size check failed"
パフォーマンスメトリクスのベースライン
| メトリクス | ベースライン | 目標 | 監視 |
|---|---|---|---|
| メインバンドル | 759 KB | < 500 KB | verify:build |
| 初回画面読み込み | ~2s | < 2s | Lighthouse |
| 総バンドル | 6.24 MB | < 3 MB | analyze:build |
| チャンク数 | 76 | - | analyze:build |