Skip to content

Build System

Makoto Horikawa edited this page Jul 9, 2025 · 1 revision

⚡ ビルドシステム

GitHub Power Scouterのビルドシステム(Vite)の詳細解説です。初心者からベテランまで、ビルドシステムの理解と最適化について包括的に説明します。

🏗️ Viteビルドシステム概要

flowchart TD
    A[Source Code] --> B{Development Mode?}
    B -->|Yes| C[Vite Dev Server]
    B -->|No| D[Production Build]
    
    C --> E[ES Modules]
    C --> F[HMR]
    C --> G[Fast Refresh]
    
    D --> H[Bundle Analysis]
    D --> I[Code Splitting]
    D --> J[Asset Optimization]
    
    H --> K[Rollup]
    I --> K
    J --> K
    
    K --> L[Optimized Assets]
    L --> M[Static Files]
Loading

🎯 Viteが選ばれる理由

従来ツールとの比較

特徴 Webpack Parcel Vite 優位性
開発サーバー起動 10-30秒 5-15秒 0.5-2秒 ✅ 10-50倍高速
HMR 1-5秒 1-3秒 0.1-0.5秒 ✅ 瞬時更新
学習コスト ✅ 簡単設定
エコシステム 最大 成長中 ⚠️ まだ発展途上

Viteのアーキテクチャ

graph LR
    subgraph "Development"
        A[Source Files] --> B[esbuild Transform]
        B --> C[ES Modules]
        C --> D[Browser]
    end
    
    subgraph "Production"
        E[Source Files] --> F[Rollup Bundle]
        F --> G[Optimized Bundle]
        G --> H[Static Assets]
    end
Loading

🔧 設定ファイル詳細

vite.config.ts 完全解説

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'

export default defineConfig({
  // プラグイン設定
  plugins: [
    react({
      // React Fast Refresh設定
      fastRefresh: true,
      // JSX運用ランタイム設定
      jsxRuntime: 'automatic',
    }),
  ],
  
  // 開発サーバー設定
  server: {
    port: 5173,
    host: true, // ネットワークアクセス許可
    open: true, // ブラウザ自動オープン
    cors: true, // CORS設定
    proxy: {
      // APIプロキシ設定(開発時のCORS回避)
      '/api': {
        target: 'https://api.github.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
  
  // ビルド設定
  build: {
    // 出力ディレクトリ
    outDir: 'dist',
    
    // アセットディレクトリ
    assetsDir: 'assets',
    
    // ソースマップ生成
    sourcemap: process.env.NODE_ENV !== 'production',
    
    // minify設定
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true, // console.log削除
        drop_debugger: true, // debugger文削除
      },
    },
    
    // チャンク分割設定
    rollupOptions: {
      output: {
        manualChunks: {
          // ベンダーチャンク
          vendor: ['react', 'react-dom'],
          // ユーティリティチャンク
          utils: ['./src/utils/index.ts'],
        },
        
        // アセット命名規則
        assetFileNames: (assetInfo) => {
          const info = assetInfo.name.split('.')
          const ext = info[info.length - 1]
          
          // 画像ファイル
          if (/\.(png|jpe?g|svg|gif|tiff|bmp|ico)$/i.test(assetInfo.name)) {
            return `images/[name]-[hash][extname]`
          }
          
          // フォントファイル
          if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name)) {
            return `fonts/[name]-[hash][extname]`
          }
          
          return `${ext}/[name]-[hash][extname]`
        },
        
        // JSファイル命名規則
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
      },
    },
    
    // ファイルサイズ警告しきい値
    chunkSizeWarningLimit: 1000, // 1MB
  },
  
  // ベースパス設定(デプロイ先に応じて変更)
  base: process.env.NODE_ENV === 'production'
    ? '/programming-skill-scouter/' // GitHub Pages
    : './',
  
  // 解決設定
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@components': resolve(__dirname, 'src/components'),
      '@types': resolve(__dirname, 'src/types'),
      '@utils': resolve(__dirname, 'src/utils'),
    },
  },
  
  // CSS設定
  css: {
    modules: {
      localsConvention: 'camelCase',
    },
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`,
      },
    },
  },
  
  // 環境変数設定
  define: {
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
    __BUILD_TIME__: JSON.stringify(new Date().toISOString()),
  },
})

🚀 npm スクリプト詳細

package.json scripts

{
  "scripts": {
    // 開発サーバー起動
    "dev": "vite",
    "dev:host": "vite --host", // ネットワークアクセス有効
    "dev:debug": "vite --debug", // デバッグモード
    
    // ビルド関連
    "build": "tsc -b && vite build",
    "build:analyze": "npm run build && npx vite-bundle-analyzer dist",
    "build:preview": "npm run build && npm run preview",
    
    // 型チェック
    "type-check": "tsc --noEmit",
    "type-check:watch": "tsc --noEmit --watch",
    
    // リント・フォーマット
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "lint:fix": "eslint . --ext ts,tsx --fix",
    "format": "prettier --write \"src/**/*.{ts,tsx}\"",
    "format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
    
    // プレビュー
    "preview": "vite preview",
    "preview:host": "vite preview --host",
    
    // デプロイ
    "predeploy": "npm run build",
    "deploy": "gh-pages -d dist",
    
    // クリーンアップ
    "clean": "rm -rf dist node_modules/.vite",
    "fresh": "npm run clean && npm install"
  }
}

スクリプト実行例

# 開発環境での作業フロー
npm run dev              # 開発サーバー起動
npm run type-check       # 型チェック
npm run lint             # リント実行
npm run format           # コードフォーマット

# ビルド・デプロイフロー
npm run build           # プロダクションビルド
npm run build:analyze   # バンドル分析
npm run preview         # ビルド結果プレビュー
npm run deploy          # デプロイ

📊 バンドル最適化

コード分割戦略

graph TD
    A[Application Bundle] --> B[Entry Chunk]
    A --> C[Vendor Chunk]
    A --> D[Utils Chunk]
    A --> E[Dynamic Chunks]
    
    B --> F[App.tsx]
    B --> G[main.tsx]
    
    C --> H[react]
    C --> I[react-dom]
    
    D --> J[API utilities]
    D --> K[Helper functions]
    
    E --> L[Lazy Components]
    E --> M[Route Components]
Loading

動的インポートの実装

// コンポーネントの遅延読み込み
import { lazy, Suspense } from 'react'

// 動的インポート
const ResumeModal = lazy(() => import('./ResumeModal'))
const HeavyComponent = lazy(() => 
  import('./HeavyComponent').then(module => ({
    default: module.HeavyComponent
  }))
)

// 使用例
function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <ResumeModal />
    </Suspense>
  )
}

Tree Shaking最適化

// ❌ 悪い例: ライブラリ全体をインポート
import * as utils from 'lodash'

// ✅ 良い例: 必要な関数のみインポート
import { debounce, throttle } from 'lodash'

// ✅ さらに良い例: 個別モジュールインポート
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'

🔍 バンドル分析

バンドルサイズ分析ツール

# vite-bundle-analyzerでの分析
npm install --save-dev vite-bundle-analyzer
npx vite-bundle-analyzer dist

# rollup-plugin-visualizerでの分析
npm install --save-dev rollup-plugin-visualizer
npm run build

分析結果の読み方

pie title Bundle Size Distribution
    "React & ReactDOM" : 45
    "Application Code" : 30
    "Third-party Libraries" : 15
    "CSS & Assets" : 10
Loading

最適化指標

メトリクス 目標値 現在値 状態
Total Bundle Size < 1MB ~220KB
Initial JS < 500KB ~205KB
CSS < 100KB ~7KB
Vendor Chunk < 200KB ~12KB
Gzip Compression < 70% ~65%

⚙️ 開発時最適化

HMR(Hot Module Replacement)設定

// HMRのカスタマイズ
if (import.meta.hot) {
  // モジュール受け入れ設定
  import.meta.hot.accept()
  
  // データ保持設定
  import.meta.hot.accept(['./UserData'], (newModule) => {
    // 状態を保持しながらモジュール更新
  })
  
  // 破棄時のクリーンアップ
  import.meta.hot.dispose(() => {
    // クリーンアップ処理
  })
}

開発時専用機能

// 開発時のみ有効なデバッグ機能
if (import.meta.env.DEV) {
  // React DevTools
  window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.__REACT_DEVTOOLS_GLOBAL_HOOK__ || {};
  
  // パフォーマンス測定
  console.time('Component Render')
  
  // API レスポンス時間測定
  const startTime = performance.now()
}

🚀 プロダクションビルド最適化

アセット最適化

// vite.config.ts での画像最適化
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    rollupOptions: {
      external: [
        // 外部依存として扱うライブラリ
      ],
      output: {
        // 画像の最適化
        assetFileNames: (assetInfo) => {
          if (/\.(png|jpe?g|svg|gif)$/i.test(assetInfo.name)) {
            return 'images/[name]-[hash][extname]'
          }
          return 'assets/[name]-[hash][extname]'
        }
      }
    }
  },
  
  // アセット処理設定
  assetsInclude: ['**/*.md'], // Markdownファイルをアセットとして処理
})

圧縮設定

// Gzip/Brotli圧縮
import { defineConfig } from 'vite'
import { compression } from 'vite-plugin-compression'

export default defineConfig({
  plugins: [
    compression({
      algorithm: 'gzip',
      threshold: 1024, // 1KB以上のファイルを圧縮
    }),
    compression({
      algorithm: 'brotliCompress',
      ext: '.br',
      threshold: 1024,
    }),
  ],
})

🔧 カスタムプラグイン

GitHub APIキャッシュプラグイン

// plugins/github-cache.ts
import type { Plugin } from 'vite'

export function githubCachePlugin(): Plugin {
  return {
    name: 'github-cache',
    configureServer(server) {
      server.middlewares.use('/api/github', (req, res, next) => {
        // GitHub APIレスポンスのキャッシュ処理
        const cacheKey = req.url
        
        // キャッシュから取得を試行
        const cached = getCache(cacheKey)
        if (cached) {
          res.setHeader('Content-Type', 'application/json')
          res.end(JSON.stringify(cached))
          return
        }
        
        next()
      })
    },
  }
}

ビルド時処理プラグイン

// plugins/build-info.ts
import type { Plugin } from 'vite'

export function buildInfoPlugin(): Plugin {
  return {
    name: 'build-info',
    generateBundle() {
      // ビルド情報をJSONファイルとして出力
      const buildInfo = {
        timestamp: new Date().toISOString(),
        version: process.env.npm_package_version,
        environment: process.env.NODE_ENV,
      }
      
      this.emitFile({
        type: 'asset',
        fileName: 'build-info.json',
        source: JSON.stringify(buildInfo, null, 2),
      })
    },
  }
}

📈 パフォーマンス監視

ビルド時間監視

// plugins/build-timer.ts
import type { Plugin } from 'vite'

export function buildTimerPlugin(): Plugin {
  let startTime: number
  
  return {
    name: 'build-timer',
    buildStart() {
      startTime = Date.now()
      console.log('🚀 Build started...')
    },
    generateBundle() {
      const duration = Date.now() - startTime
      console.log(`✅ Build completed in ${duration}ms`)
    },
  }
}

バンドルサイズ監視

# size-limit での監視
npm install --save-dev size-limit @size-limit/preset-big-lib

# package.json
{
  "size-limit": [
    {
      "path": "dist/assets/*.js",
      "limit": "500 KB"
    },
    {
      "path": "dist/assets/*.css",
      "limit": "50 KB"
    }
  ]
}

🚨 トラブルシューティング

よくある問題と解決方法

1. ビルドが遅い

# 並列処理の有効化
export VITE_BUILD_THREADS=$(nproc)

# キャッシュのクリア
rm -rf node_modules/.vite
npm run build

2. メモリ不足エラー

# Node.jsのメモリ制限を増加
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build

3. アセットが読み込めない

// 正しい相対パス設定
export default defineConfig({
  base: process.env.NODE_ENV === 'production' 
    ? '/your-repo-name/' 
    : './',
})

📊 CI/CD統合

GitHub Actions設定

# .github/workflows/build.yml
name: Build and Test

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Type check
      run: npm run type-check
    
    - name: Lint
      run: npm run lint
    
    - name: Build
      run: npm run build
    
    - name: Size check
      run: npx size-limit
    
    - name: Upload build artifacts
      uses: actions/upload-artifact@v4
      with:
        name: build-files
        path: dist/

🔮 将来の最適化

次世代ツールへの移行計画

timeline
    title Build Tool Evolution
    
    Current : Vite 7.0
            : esbuild
            : Rollup
    
    Q2 2024 : Vite 8.0
            : Rollup 5.0
            : Lightning CSS
    
    Q4 2024 : Native ESM
            : Import Maps
            : Web Assembly
    
    2025    : Rust-based tools
            : Even faster builds
            : Zero-config setup
Loading

次のステップ: リント・フォーマットガイドでコード品質管理を学びましょう。

Clone this wiki locally