Vite

ビルドツールJavaScript高速フロントエンド次世代ES modules

ビルドツール

Vite

概要

Viteは、フロントエンド開発用の高速ビルドツールです。Vue.jsの作者であるEvan You氏によって開発され、「lightning fast」(稲妻のように高速)をコンセプトとしています。ES modulesとRollupを活用することで、開発時は極めて高速なコールドスタートとホットモジュールリプレースメント(HMR)を実現し、本番ビルド時はRollupベースの最適化を行います。VueやReact、Svelte、TypeScriptなど幅広い技術スタックをサポートし、次世代フロントエンド開発ツールとして急速に普及しています。

詳細

主要機能

  • 高速開発サーバー: ネイティブESモジュールを活用した即座の起動
  • ホットモジュールリプレースメント: 状態を保持した高速更新
  • TypeScript統合: 設定なしのTypeScriptサポート
  • CSS処理: PostCSS、Sass、Lessなどの自動サポート
  • 最適化ビルド: Rollupベースのプロダクションビルド
  • プラグインエコシステム: Rollupと互換性のある豊富なプラグイン
  • 多フレームワーク対応: Vue、React、Svelte、Vanillaなど

アーキテクチャ

開発時はネイティブESモジュールを直接ブラウザに配信し、本番時はRollupで最適化。依存関係の事前バンドリング(esbuild使用)により高速化を実現。

パフォーマンス特徴

  • 開発サーバー起動: 数百ミリ秒での起動
  • HMR: 50ms以下での更新
  • 事前バンドリング: esbuildによる10-100倍高速な依存関係処理
  • インクリメンタルビルド: 変更されたモジュールのみの処理

メリット・デメリット

メリット

  • 圧倒的な高速性: 開発サーバー起動とHMRが極めて高速
  • ゼロ設定: 多くの場合、設定なしで動作開始可能
  • 現代的なアーキテクチャ: ESモジュールとesbuildの活用
  • 軽量: 依存関係が少なく、インストールが高速
  • 優れた開発体験: 高速フィードバックループ
  • TypeScript完全対応: 設定なしのTypeScriptサポート
  • 豊富なフレームワーク対応: Vue、React、Svelteなど公式サポート
  • プラグインエコシステム: Rollupプラグインと互換性

デメリット

  • 新しいツール: Webpackほどの実績とコミュニティがない
  • プラグイン不足: 一部の特殊なプラグインは未対応
  • レガシーブラウザ: 古いブラウザでは設定が複雑
  • 学習コストの転換: Webpackから移行時の概念の違い
  • 大規模プロジェクト: 一部の大規模・複雑なプロジェクトでは制限も
  • デバッグ情報: エラーメッセージがWebpackより少ない場合も

参考ページ

書き方の例

プロジェクトの作成とセットアップ

# 新規プロジェクト作成(フレームワーク選択付き)
npm create vite@latest my-app
cd my-app
npm install

# 特定フレームワークでの作成
npm create vite@latest my-vue-app -- --template vue
npm create vite@latest my-react-app -- --template react
npm create vite@latest my-svelte-app -- --template svelte
npm create vite@latest my-ts-app -- --template vanilla-ts

# 既存プロジェクトにViteを追加
npm install --save-dev vite

# 開発サーバー起動
npm run dev

# 本番ビルド
npm run build

# プレビューサーバー(ビルド結果確認)
npm run preview

基本的なvite.config.js設定

import { defineConfig } from 'vite'

export default defineConfig({
  // ルートディレクトリ設定
  root: '.',
  
  // 開発サーバー設定
  server: {
    port: 3000,
    open: true,  // ブラウザ自動オープン
    host: true,  // 外部アクセス許可
    cors: true   // CORS有効化
  },
  
  // ビルド設定
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: true,  // ソースマップ生成
    minify: 'terser', // 圧縮方法
    target: 'es2015'  // ターゲットブラウザ
  },
  
  // ベースパス設定
  base: './',
  
  // プレビューサーバー設定
  preview: {
    port: 4173,
    open: true
  }
})

React + TypeScript設定

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

export default defineConfig({
  plugins: [react()],
  
  // パスエイリアス設定
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@utils': path.resolve(__dirname, './src/utils')
    }
  },
  
  // 開発サーバー設定
  server: {
    port: 3000,
    hot: true,
    
    // プロキシ設定(API サーバー)
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  
  // CSS設定
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      }
    },
    modules: {
      localsConvention: 'camelCaseOnly'
    }
  },
  
  // 最適化設定
  optimizeDeps: {
    include: ['react', 'react-dom'],
    exclude: ['some-large-library']
  },
  
  // ビルド設定
  build: {
    target: 'es2015',
    outDir: 'dist',
    sourcemap: true,
    
    // ロールアップオプション
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          router: ['react-router-dom']
        }
      }
    }
  }
})

Vue 3 + TypeScript設定

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

export default defineConfig({
  plugins: [
    vue({
      // Vue SFC カスタムブロック
      include: [/\.vue$/],
      // TypeScript JSX サポート
      script: {
        defineModel: true,
        propsDestructure: true
      }
    })
  ],
  
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
      '~': resolve(__dirname, './src')
    }
  },
  
  server: {
    port: 5173,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true
      }
    }
  },
  
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `
          @import "@/styles/variables.scss";
          @import "@/styles/mixins.scss";
        `
      }
    }
  },
  
  build: {
    target: 'esnext',
    minify: 'esbuild',
    cssCodeSplit: true
  }
})

高度な設定とプラグイン活用

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
import legacy from '@vitejs/plugin-legacy'
import { visualizer } from 'rollup-plugin-visualizer'
import eslint from 'vite-plugin-eslint'

export default defineConfig({
  plugins: [
    react(),
    
    // ESLint統合
    eslint({
      cache: false,
      include: ['./src/**/*.js', './src/**/*.jsx', './src/**/*.ts', './src/**/*.tsx'],
      exclude: []
    }),
    
    // レガシーブラウザサポート
    legacy({
      targets: ['defaults', 'not IE 11']
    }),
    
    // バンドル分析
    visualizer({
      filename: 'dist/stats.html',
      open: false,
      gzipSize: true
    })
  ],
  
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
      '@assets': resolve(__dirname, './src/assets'),
      '@components': resolve(__dirname, './src/components'),
      '@hooks': resolve(__dirname, './src/hooks'),
      '@services': resolve(__dirname, './src/services'),
      '@utils': resolve(__dirname, './src/utils')
    },
    
    // 拡張子省略
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
  },
  
  // 環境変数設定
  define: {
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
    __BUILD_TIME__: JSON.stringify(new Date().toISOString())
  },
  
  server: {
    port: 3000,
    host: '0.0.0.0',
    
    // HTTPS設定
    https: false,
    
    // プロキシ設定(複数API)
    proxy: {
      '/api/v1': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api\/v1/, '/api/v1')
      },
      '/api/v2': {
        target: 'http://localhost:8081',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api\/v2/, '/api/v2')
      }
    }
  },
  
  css: {
    devSourcemap: true,
    preprocessorOptions: {
      scss: {
        additionalData: `
          @import "@/styles/variables.scss";
          @import "@/styles/mixins.scss";
          @import "@/styles/functions.scss";
        `
      }
    },
    postcss: {
      plugins: [
        require('autoprefixer'),
        require('cssnano')({
          preset: 'default'
        })
      ]
    }
  },
  
  // 事前バンドリング設定
  optimizeDeps: {
    include: [
      'react',
      'react-dom',
      'react-router-dom',
      'axios',
      'lodash-es'
    ],
    exclude: ['some-es-module-library'],
    
    // esbuildオプション
    esbuildOptions: {
      define: {
        global: 'globalThis'
      }
    }
  },
  
  build: {
    target: ['es2015', 'chrome79', 'safari13'],
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: 'hidden',
    minify: 'terser',
    
    // テラーオプション
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    },
    
    // チャンク分割
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        admin: resolve(__dirname, 'admin.html')
      },
      
      output: {
        manualChunks: {
          react: ['react', 'react-dom'],
          router: ['react-router-dom'],
          ui: ['@mui/material', '@mui/icons-material'],
          utils: ['lodash-es', 'date-fns'],
          chart: ['chart.js', 'react-chartjs-2']
        },
        
        // アセット命名規則
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: ({ name }) => {
          if (/\.(gif|jpe?g|png|svg)$/.test(name ?? '')) {
            return 'images/[name]-[hash][extname]'
          }
          if (/\.css$/.test(name ?? '')) {
            return 'css/[name]-[hash][extname]'
          }
          return 'assets/[name]-[hash][extname]'
        }
      }
    },
    
    // ファイルサイズ警告の閾値
    chunkSizeWarningLimit: 1000
  }
})

環境別設定

import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig(({ command, mode }) => {
  // 環境変数を読み込み
  const env = loadEnv(mode, process.cwd(), '')
  
  return {
    plugins: [react()],
    
    // 開発とプロダクションで設定を切り替え
    server: {
      port: command === 'serve' ? 3000 : undefined,
      open: command === 'serve' ? true : false
    },
    
    build: {
      // 本番環境のみソースマップを生成
      sourcemap: mode === 'production' ? 'hidden' : true,
      
      // 本番環境のみファイル圧縮
      minify: mode === 'production' ? 'terser' : false
    },
    
    // 環境変数をクライアントに公開
    define: {
      __API_URL__: JSON.stringify(env.VITE_API_URL),
      __APP_VERSION__: JSON.stringify(env.npm_package_version)
    }
  }
})

ライブラリ作成用の設定

import { defineConfig } from 'vite'
import { resolve } from 'path'
import dts from 'vite-plugin-dts'

export default defineConfig({
  plugins: [
    dts({
      insertTypesEntry: true  // 型定義ファイル自動生成
    })
  ],
  
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'MyLibrary',
      formats: ['es', 'umd'],  // ESとUMD形式で出力
      fileName: (format) => `my-library.${format}.js`
    },
    
    rollupOptions: {
      // 外部依存関係(バンドルしない)
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM'
        }
      }
    },
    
    // ソースマップ生成
    sourcemap: true,
    
    // 出力先
    outDir: 'dist'
  }
})

テスト統合(Vitest)

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

export default defineConfig({
  plugins: [react()],
  
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
      '@test': resolve(__dirname, './test')
    }
  },
  
  // Vitest設定
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts'],
    css: true,
    
    // カバレッジ設定
    coverage: {
      provider: 'c8',
      reporter: ['text', 'html', 'lcov'],
      exclude: [
        'node_modules/',
        'src/test/',
        '**/*.d.ts'
      ]
    }
  }
})