Vite
ビルドツール
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'
]
}
}
})