T-CREATOR

Vite でパフォーマンスを追求するプロファイリング手法

Vite でパフォーマンスを追求するプロファイリング手法

Vite を使ったフロントエンド開発において、「なぜビルドが遅いのか?」「開発サーバーの起動に時間がかかるのはなぜか?」といった疑問を抱えたことはありませんか?

現代の Web アプリケーション開発では、パフォーマンスが直接的にユーザー体験や開発効率に影響します。Vite は高速な開発体験を提供してくれる素晴らしいツールですが、プロジェクトが成長するにつれて、適切なプロファイリング手法を身につけることが、真の意味でのパフォーマンス最適化への第一歩となるのです。

背景

Vite のビルドシステムとパフォーマンス特性

Vite は、ES ビルドと Rollup を組み合わせた革新的なビルドツールです。開発時には ES modules を活用した高速な HMR(Hot Module Replacement)を提供し、本番ビルド時には Rollup による最適化を行います。

しかし、この二段構えのアプローチゆえに、パフォーマンス問題の原因を特定するには、それぞれの段階での計測が必要になります。

typescript// vite.config.ts - 基本的な設定例
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
        },
      },
    },
  },
});

モダンフロントエンド開発におけるパフォーマンス要求の高まり

現在の Web アプリケーションは、ユーザーが期待する応答性が以前とは比較にならないほど高くなっています。Google の研究によると、ページの読み込み時間が 1 秒から 3 秒に増加すると、直帰率は 32%増加するとされています。

開発者にとっても、ビルド時間の短縮は生産性に直結します。1 日に何十回もビルドを実行する開発現場では、わずか数秒の改善でも大きな効果をもたらします。

プロファイリングによる可視化の必要性

「感覚的に遅い」から「具体的にどこが遅いのか」を知ることで、初めて適切な対策を講じることができます。プロファイリングは、パフォーマンス問題を推測ではなく、データに基づいて解決するための必須スキルなのです。

課題

ビルド時間の増大問題

プロジェクトの規模が大きくなるにつれて、以下のような典型的なエラーメッセージに遭遇することがあります:

bash# よく見られるビルドタイムアウトエラー
Error: Timeout of 60000ms exceeded.
    at Timeout.<anonymous> (/node_modules/rollup/dist/rollup.js:25123:24)
    at listOnTimeout (node:internal/timers:559:17)
    at processTimers (node:internal/timers:502:7)

このエラーは、ビルドプロセスが期待される時間内に完了しなかったことを示しています。大規模なプロジェクトでは、数百から数千のモジュールを処理する必要があり、依存関係の解析だけでも相当な時間を要します。

開発サーバーの起動速度低下

開発サーバーの起動が遅い場合、以下のようなログが出力されることがあります:

bash# 開発サーバー起動時の警告例
vite v4.4.5 dev server running at:
> Local:   http://localhost:5173/
> Network: use --host to expose

ready in 12.3s
⚠ Large number of files detected (2847).
Consider using .gitignore to exclude unnecessary files.

このような警告は、Vite が多数のファイルを監視しようとしていることを示しています。不要なファイルまで監視対象に含まれている可能性があります。

バンドルサイズの肥大化

本番ビルド後に予想以上にファイルサイズが大きくなってしまうケースも頻繁に発生します:

bash# ビルド結果の例(サイズが大きすぎる場合)
dist/assets/index-a1b2c3d4.js   2,847.23 kB │ gzip: 856.34 kB
⚠ Some chunks are larger than 500 kBs after minification.
Consider using dynamic imports or code splitting.

HMR(Hot Module Replacement)の遅延

開発時に特に問題となるのが、HMR の応答速度です。ファイルを変更してから反映されるまでの時間が長いと、開発体験が著しく悪化します:

javascript// HMR エラーの例
[vite] hmr update /src/components/LargeComponent.tsx failed:
Error: Failed to reload /src/components/LargeComponent.tsx.
This could be due to syntax errors or dependency issues.
    at updateModules (vite/client:247:15)
    at handleMessage (vite/client:123:7)

解決策

Vite 内蔵のプロファイリング機能の活用

Vite には強力な内蔵プロファイリング機能が搭載されています。まず基本的な--profileオプションから始めてみましょう:

bash# 基本的なプロファイリングコマンド
yarn vite build --profile

# より詳細な情報を取得
yarn vite build --profile --logLevel info

このコマンドを実行すると、以下のような出力が得られます:

bash# プロファイリング結果の例
✓ building for production...
✓ 247 modules transformed.

Build performance:
- Plugin execution: 1.2s
- Rollup bundling: 3.4s
- Asset processing: 0.8s
- Total build time: 5.4s

Top 5 slowest plugins:
1. @vitejs/plugin-react: 456ms
2. vite:build-html: 234ms
3. vite:terser: 189ms
4. vite:css: 167ms
5. vite:asset: 143ms

プラグインパフォーマンスの計測手法

個別のプラグインのパフォーマンスを詳細に計測するには、カスタムの計測ロジックを実装することが効果的です:

typescript// プラグインパフォーマンス計測の実装
import { Plugin } from 'vite';

function createPerformancePlugin(): Plugin {
  const startTimes = new Map<string, number>();

  return {
    name: 'performance-monitor',
    configResolved() {
      console.log('🚀 Performance monitoring started');
    },
    buildStart() {
      startTimes.set('build', Date.now());
    },
    generateBundle() {
      const buildTime =
        Date.now() - startTimes.get('build')!;
      console.log(`📊 Build completed in ${buildTime}ms`);
    },
  };
}

依存関係分析とボトルネック特定

依存関係の分析には、専用のプラグインを活用します:

bash# Bundle Analyzerのインストール
yarn add --dev rollup-plugin-visualizer

# プラグインの設定後、分析実行
yarn vite build --mode production

設定ファイルでの実装例:

typescript// vite.config.ts - Bundle Analyzer設定
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    // 他のプラグイン...
    visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true,
    }),
  ],
});

メモリ使用量の監視方法

Node.js のメモリ使用量を監視するスクリプトを作成しましょう:

javascript// scripts/memory-monitor.js
function formatBytes(bytes) {
  return (
    Math.round((bytes / 1024 / 1024) * 100) / 100 + ' MB'
  );
}

function logMemoryUsage() {
  const usage = process.memoryUsage();
  console.log(`🧠 Memory Usage:`);
  console.log(`   RSS: ${formatBytes(usage.rss)}`);
  console.log(
    `   Heap Used: ${formatBytes(usage.heapUsed)}`
  );
  console.log(
    `   Heap Total: ${formatBytes(usage.heapTotal)}`
  );
  console.log(
    `   External: ${formatBytes(usage.external)}`
  );
}

// 定期的にメモリ使用量をログ出力
setInterval(logMemoryUsage, 5000);

具体例

vite --profileコマンドの実践的活用

実際のプロジェクトでプロファイリングを実行してみましょう。以下は、典型的な大規模 React プロジェクトでの事例です:

bash# 詳細プロファイリングの実行
yarn vite build --profile --mode production

# 出力例
vite v4.4.5 building for production...
✓ 1,247 modules transformed.
✓ built in 23.45s

Performance breakdown:
┌─────────────────────────┬────────────┬─────────┐
│ Phase                   │ Time (ms)  │ Percent │
├─────────────────────────┼────────────┼─────────┤
│ Plugin Resolution       │ 2,340      │ 9.9%    │
│ Module Transformation   │ 12,890     │ 55.0%   │
│ Rollup Bundling        │ 6,780      │ 28.9%   │
│ Asset Optimization     │ 1,440      │ 6.1%    │
└─────────────────────────┴────────────┴─────────┘

この結果から、モジュール変換の段階で最も時間がかかっていることがわかります。

Bundle Analyzer を使った詳細分析

Bundle Analyzer の実行結果から問題を特定する例:

typescript// 問題のあるimportの例
import * as _ from 'lodash'; // 😰 全体をimport(約70KB)

// 改善されたimport
import { debounce, throttle } from 'lodash-es'; // 😊 必要な関数のみ

Analyzer の結果から、lodash 全体が含まれていることが判明した場合の対処法:

typescript// vite.config.ts - Tree Shakingの改善
export default defineConfig({
  build: {
    rollupOptions: {
      external: ['lodash'],
      output: {
        globals: {
          lodash: '_',
        },
      },
    },
  },
  optimizeDeps: {
    include: ['lodash-es'],
  },
});

Node.js Profiler との連携テクニック

より詳細なプロファイリングには、Node.js の内蔵プロファイラーを活用します:

bash# Node.js プロファイラーでViteを実行
node --prof node_modules/.bin/vite build

# プロファイル結果の解析
node --prof-process isolate-*.log > vite-profile.txt

プロファイル結果の読み方:

bash# プロファイル結果の例(抜粋)
[Summary]:
   ticks  total  nonlib   name
    892   17.8%   17.8%  JavaScript
   3,456   69.1%   69.1%  C++
    234    4.7%    4.7%  GC
     89    1.8%          Shared libraries
    329    6.6%          Unaccounted

[JavaScript]:
   ticks  total  nonlib   name
    234    4.7%    4.7%  LazyCompile: transform /node_modules/esbuild/lib/main.js:1234:15
    189    3.8%    3.8%  LazyCompile: parse /node_modules/rollup/dist/rollup.js:5678:20

カスタムプロファイリングスクリプトの実装

開発チーム独自のプロファイリングスクリプトを作成することで、継続的な監視が可能になります:

javascript// scripts/build-profiler.js
import { performance } from 'perf_hooks';
import { exec } from 'child_process';
import fs from 'fs';

class BuildProfiler {
  constructor() {
    this.metrics = {};
    this.startTime = 0;
  }

  start() {
    this.startTime = performance.now();
    console.log('🎯 Build profiling started...');
  }

  markPhase(phaseName) {
    const currentTime = performance.now();
    this.metrics[phaseName] = currentTime - this.startTime;
    console.log(
      `✅ ${phaseName}: ${this.metrics[phaseName].toFixed(
        2
      )}ms`
    );
  }

  async runBuild() {
    this.start();

    return new Promise((resolve, reject) => {
      exec('yarn vite build', (error, stdout, stderr) => {
        this.markPhase('Complete Build');

        if (error) {
          console.error('❌ Build failed:', error);
          reject(error);
          return;
        }

        this.saveReport();
        resolve(stdout);
      });
    });
  }

  saveReport() {
    const report = {
      timestamp: new Date().toISOString(),
      metrics: this.metrics,
      totalTime: this.metrics['Complete Build'],
    };

    fs.writeFileSync(
      `build-reports/report-${Date.now()}.json`,
      JSON.stringify(report, null, 2)
    );

    console.log('📊 Performance report saved');
  }
}

// 使用例
const profiler = new BuildProfiler();
profiler.runBuild().catch(console.error);

継続的なパフォーマンス監視のための設定:

json// package.json - カスタムスクリプト
{
  "scripts": {
    "build:profile": "node scripts/build-profiler.js",
    "build:analyze": "yarn build && yarn analyze",
    "analyze": "npx serve dist/stats.html"
  }
}

まとめ

Vite でのパフォーマンスプロファイリングは、単なる技術的な作業以上の価値があります。それは、あなたの開発体験を根本的に向上させ、ユーザーにより良いプロダクトを届けるための投資なのです。

今回ご紹介した手法を実践することで、以下のような成果を期待できます:

  • ビルド時間の 30-50%短縮:適切なプロファイリングにより、ボトルネックを特定し、的確な最適化が可能
  • 開発効率の大幅向上:HMR の高速化により、コード変更からプレビューまでの時間を短縮
  • バンドルサイズの最適化:不要な依存関係を除去し、ユーザー体験の向上を実現

パフォーマンスプロファイリングは継続的なプロセスです。一度設定すれば終わりではなく、プロジェクトの成長とともに定期的に見直し、改善を続けることが重要です。

あなたの次のプロジェクトでも、ぜひこれらの手法を活用して、より快適で効率的な開発体験を実現してください。きっと、コードを書くことがもっと楽しくなるはずです。

関連リンク