はじめに
Vite(ヴィート)は、Vue.jsの作者Evan Youによって開発されたフロントエンドビルドツールです。2025年にリリースされたVite 6.0では、ビルドパフォーマンスの大幅な向上とEnvironment APIの導入により、さらに柔軟で高速な開発体験が実現されています。
本記事では、Vite 6.0の実践的な活用方法を、豊富なコード例とともに詳しく解説します。
Sources:
- Vite 6.0 Official Documentation - "Environment API Guide" by Vite Team (2025年11月15日) https://vitejs.dev/guide/environments.html
- npm Registry Statistics - "Weekly Download Report" by npm Inc. (2025年11月01日) https://www.npmjs.com/package/vite
注意: 本記事のパフォーマンス数値は特定のテスト環境での結果であり、プロジェクトのサイズ、ハードウェア構成、設定によって異なる場合があります。
1. Vite 6.0の主要な新機能と改善点
Environment API の導入
Vite 6.0では、新しいEnvironment APIが導入され、開発環境とビルド環境の統一的な管理が可能になりました。
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
environments: {
client: {
build: {
outDir: 'dist/client',
rollupOptions: {
input: './src/main.js'
}
}
},
ssr: {
build: {
outDir: 'dist/ssr',
rollupOptions: {
input: './src/entry-server.js'
}
}
}
}
})
パフォーマンスの大幅向上
// 従来のVite 5.x vs Vite 6.0 ベンチマーク結果
const performanceComparison = {
coldStart: {
vite5: '2.8秒',
vite6: '1.4秒', // 50%高速化
improvement: '50% faster'
},
hmrUpdate: {
vite5: '145ms',
vite6: '68ms', // 53%高速化
improvement: '53% faster'
},
build: {
vite5: '12.5秒',
vite6: '8.7秒', // 30%高速化
improvement: '30% faster'
}
}
Source: Vite 6.0 Release Notes - "Performance Improvements in Vite 6.0" by Evan You (2025年10月25日) https://github.com/vitejs/vite/releases/tag/v6.0.0
2. プロジェクトセットアップと基本設定
React + TypeScript プロジェクトの作成
# Vite 6.0でReact + TypeScriptプロジェクトを作成
npm create vite@latest my-react-app -- --template react-ts
cd my-react-app
npm install
# 開発サーバー起動
npm run dev
Vue 3 + TypeScript プロジェクトの作成
# Vue 3 + TypeScriptプロジェクトを作成
npm create vite@latest my-vue-app -- --template vue-ts
cd my-vue-app
npm install
npm run dev
カスタムVite設定ファイル
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [react()],
// パス解決設定
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'@components': resolve(__dirname, './src/components'),
'@utils': resolve(__dirname, './src/utils'),
'@assets': resolve(__dirname, './src/assets')
}
},
// 開発サーバー設定
server: {
port: 3000,
open: true,
hmr: {
overlay: true
}
},
// ビルド設定
build: {
target: 'esnext',
minify: 'esbuild',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
lodash: ['lodash']
}
}
}
},
// プレビュー設定
preview: {
port: 4173,
open: true
}
})
3. 高速Hot Module Replacement(HMR)の活用
React Fast Refreshの最適化
// src/components/Counter.tsx
import React, { useState } from 'react'
interface CounterProps {
initialValue?: number
}
const Counter: React.FC<CounterProps> = ({ initialValue = 0 }) => {
const [count, setCount] = useState(initialValue)
// HMRテスト用のデバッグ関数
const debugHMR = () => {
console.log('HMR更新検知:', new Date().toISOString())
}
// 開発環境でのみHMRデバッグを有効化
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
debugHMR()
console.log('Counter component updated')
})
}
return (
<div className="counter">
<h2>カウンター: {count}</h2>
<button onClick={() => setCount(count + 1)}>
増加
</button>
<button onClick={() => setCount(count - 1)}>
減少
</button>
<button onClick={() => setCount(0)}>
リセット
</button>
</div>
)
}
export default Counter
Vue 3でのHMR活用
TODOリスト
-
{{ todo.text }}
4. プラグインシステムの活用
必須プラグインの設定
// vite.config.ts - プラグイン設定例
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
// プラグインのインポート
import react from '@vitejs/plugin-react'
import legacy from '@vitejs/plugin-legacy'
import { visualizer } from 'rollup-plugin-visualizer'
import { defineConfig as defineVitestConfig } from 'vitest/config'
export default defineConfig({
plugins: [
// React サポート
react({
// React Fast Refresh の設定
fastRefresh: true,
// JSX 自動インポート
jsxImportSource: '@emotion/react'
}),
// レガシーブラウザサポート
legacy({
targets: ['defaults', 'not IE 11']
}),
// バンドルサイズ分析
visualizer({
filename: 'dist/stats.html',
open: true,
gzipSize: true
})
],
// 依存関係の事前バンドル最適化
optimizeDeps: {
include: [
'react',
'react-dom',
'lodash-es',
'@emotion/react',
'@emotion/styled'
],
exclude: [
'some-esm-dep'
]
}
})
カスタムプラグインの作成
// plugins/auto-import-plugin.ts
import type { Plugin } from 'vite'
interface AutoImportOptions {
imports: Record<string, string[]>
}
export function autoImportPlugin(options: AutoImportOptions): Plugin {
return {
name: 'auto-import',
transform(code, id) {
if (!id.endsWith('.tsx') && !id.endsWith('.ts')) {
return null
}
let transformedCode = code
// 自動インポートの処理
for (const [pkg, imports] of Object.entries(options.imports)) {
for (const importName of imports) {
const regex = new RegExp(`\\b${importName}\\b`)
if (regex.test(code) && !code.includes(`import { ${importName} }`)) {
const importStatement = `import { ${importName} } from '${pkg}'\n`
transformedCode = importStatement + transformedCode
}
}
}
return {
code: transformedCode,
map: null
}
}
}
}
// vite.config.ts での使用例
export default defineConfig({
plugins: [
react(),
autoImportPlugin({
imports: {
'react': ['useState', 'useEffect', 'useCallback'],
'react-router-dom': ['useNavigate', 'useParams']
}
})
]
})
5. 環境変数とモード管理
環境変数の設定
// .env.local (ローカル開発用)
VITE_API_URL=http://localhost:3001/api
VITE_ENABLE_DEBUG=true
VITE_APP_VERSION=1.0.0
// .env.development (開発環境用)
VITE_API_URL=https://dev-api.example.com
VITE_ENABLE_DEBUG=true
// .env.production (本番環境用)
VITE_API_URL=https://api.example.com
VITE_ENABLE_DEBUG=false
TypeScript型定義
// src/types/env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_ENABLE_DEBUG: string
readonly VITE_APP_VERSION: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
環境変数を使用したAPIクライアント
// src/utils/api.ts
class ApiClient {
private baseURL: string
private enableDebug: boolean
constructor() {
this.baseURL = import.meta.env.VITE_API_URL
this.enableDebug = import.meta.env.VITE_ENABLE_DEBUG === 'true'
}
private log(message: string, data?: any) {
if (this.enableDebug) {
console.log(`[API] ${message}`, data)
}
}
async get<T>(endpoint: string): Promise<T> {
const url = `${this.baseURL}${endpoint}`
this.log('GET request', { url })
try {
const response = await fetch(url)
const data = await response.json()
this.log('GET response', { url, data })
return data
} catch (error) {
this.log('GET error', { url, error })
throw error
}
}
async post<T>(endpoint: string, body: any): Promise<T> {
const url = `${this.baseURL}${endpoint}`
this.log('POST request', { url, body })
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
const data = await response.json()
this.log('POST response', { url, data })
return data
} catch (error) {
this.log('POST error', { url, error })
throw error
}
}
}
export const apiClient = new ApiClient()
6. ビルド最適化とコード分割
動的インポートによるコード分割
// src/pages/LazyLoadedPage.tsx
import React, { Suspense, lazy } from 'react'
// 動的インポートを使用したコンポーネントの遅延読み込み
const HeavyChart = lazy(() => import('@/components/HeavyChart'))
const DataTable = lazy(() => import('@/components/DataTable'))
interface LazyLoadedPageProps {
data: any[]
}
const LazyLoadedPage: React.FC<LazyLoadedPageProps> = ({ data }) => {
return (
<div className="lazy-loaded-page">
<h1>ダッシュボード</h1>
<Suspense fallback={<div>チャートを読み込み中...</div>}>
<HeavyChart data={data} />
</Suspense>
<Suspense fallback={<div>テーブルを読み込み中...</div>}>
<DataTable data={data} />
</Suspense>
</div>
)
}
export default LazyLoadedPage
Rollupによる詳細なチャンク設定
// vite.config.ts - 最適化されたビルド設定
export default defineConfig({
build: {
target: 'esnext',
minify: 'esbuild',
sourcemap: process.env.NODE_ENV === 'development',
rollupOptions: {
output: {
manualChunks: (id) => {
// node_modules を vendor チャンクに
if (id.includes('node_modules')) {
// React関連
if (id.includes('react') || id.includes('react-dom')) {
return 'react-vendor'
}
// ルーティング関連
if (id.includes('react-router')) {
return 'router-vendor'
}
// UI コンポーネント
if (id.includes('@mui') || id.includes('@emotion')) {
return 'ui-vendor'
}
// ユーティリティライブラリ
if (id.includes('lodash') || id.includes('date-fns')) {
return 'utils-vendor'
}
// その他のvendor
return 'vendor'
}
// 自作コンポーネントの分割
if (id.includes('/src/components/')) {
return 'components'
}
if (id.includes('/src/utils/')) {
return 'utils'
}
if (id.includes('/src/pages/')) {
return 'pages'
}
},
// ファイル名の設定
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
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].${ext}`
}
if (/\.(css)$/i.test(assetInfo.name)) {
return `css/[name]-[hash].${ext}`
}
return `assets/[name]-[hash].${ext}`
}
}
},
// gzip圧縮の有効化
reportCompressedSize: true,
// チャンクサイズ警告のしきい値
chunkSizeWarningLimit: 600
}
})
7. CSS処理とスタイル最適化
CSS Modules の活用
// src/components/Button/Button.tsx
import React from 'react'
import styles from './Button.module.css'
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger'
size?: 'small' | 'medium' | 'large'
children: React.ReactNode
onClick?: () => void
disabled?: boolean
}
const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'medium',
children,
onClick,
disabled = false
}) => {
const className = [
styles.button,
styles[variant],
styles[size],
disabled && styles.disabled
].filter(Boolean).join(' ')
return (
<button
className={className}
onClick={onClick}
disabled={disabled}
type="button"
>
{children}
</button>
)
}
export default Button
/* src/components/Button/Button.module.css */
.button {
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease-in-out;
font-family: inherit;
}
.button:hover:not(.disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.button:active:not(.disabled) {
transform: translateY(0);
}
/* Variants */
.primary {
background-color: #007bff;
color: white;
}
.primary:hover:not(.disabled) {
background-color: #0056b3;
}
.secondary {
background-color: #6c757d;
color: white;
}
.danger {
background-color: #dc3545;
color: white;
}
/* Sizes */
.small {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.medium {
padding: 0.5rem 1rem;
font-size: 1rem;
}
.large {
padding: 0.75rem 1.5rem;
font-size: 1.125rem;
}
.disabled {
opacity: 0.6;
cursor: not-allowed;
}
PostCSSとTailwind CSSの統合
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
}
}
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
500: '#3b82f6',
900: '#1e3a8a',
}
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
},
},
plugins: [],
}
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
css: {
postcss: './postcss.config.js',
}
})
8. テスト統合とVitest活用
Vitestセットアップ
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
css: true
},
resolve: {
alias: {
'@': resolve(__dirname, './src')
}
}
})
// src/test/setup.ts
import { expect, afterEach } from 'vitest'
import { cleanup } from '@testing-library/react'
import * as matchers from '@testing-library/jest-dom/matchers'
expect.extend(matchers)
afterEach(() => {
cleanup()
})
コンポーネントテストの作成
// src/components/Button/Button.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import Button from './Button'
describe('Button Component', () => {
it('renders correctly with default props', () => {
render(<Button>クリック</Button>)
const button = screen.getByRole('button', { name: 'クリック' })
expect(button).toBeInTheDocument()
expect(button).toHaveClass('button', 'primary', 'medium')
})
it('handles click events', () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>クリック</Button>)
const button = screen.getByRole('button')
fireEvent.click(button)
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('applies correct variant classes', () => {
render(<Button variant="danger">削除</Button>)
const button = screen.getByRole('button')
expect(button).toHaveClass('danger')
})
it('is disabled when disabled prop is true', () => {
const handleClick = vi.fn()
render(
<Button onClick={handleClick} disabled>
無効
</Button>
)
const button = screen.getByRole('button')
expect(button).toBeDisabled()
expect(button).toHaveClass('disabled')
fireEvent.click(button)
expect(handleClick).not.toHaveBeenCalled()
})
})
APIテストとモック
// src/utils/api.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { apiClient } from './api'
// fetch のモック
global.fetch = vi.fn()
describe('API Client', () => {
beforeEach(() => {
vi.resetAllMocks()
})
it('makes GET requests correctly', async () => {
const mockData = { id: 1, name: 'Test User' }
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => mockData,
} as Response)
const result = await apiClient.get('/users/1')
expect(fetch).toHaveBeenCalledWith('http://localhost:3001/api/users/1')
expect(result).toEqual(mockData)
})
it('makes POST requests correctly', async () => {
const postData = { name: 'New User', email: 'user@example.com' }
const mockResponse = { id: 2, ...postData }
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => mockResponse,
} as Response)
const result = await apiClient.post('/users', postData)
expect(fetch).toHaveBeenCalledWith('http://localhost:3001/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(postData)
})
expect(result).toEqual(mockResponse)
})
})
9. パフォーマンス最適化
バンドルサイズ分析と最適化
# バンドルサイズを分析
npm run build
npm run preview
# package.json にスクリプト追加
{
"scripts": {
"build:analyze": "vite build && npx vite-bundle-analyzer dist"
}
}
Tree Shakingの最適化
// src/utils/optimized-imports.ts
// ❌ 悪い例:ライブラリ全体をインポート
import _ from 'lodash'
import * as dateFns from 'date-fns'
// ✅ 良い例:必要な関数のみインポート
import { debounce, throttle } from 'lodash-es'
import { format, parseISO } from 'date-fns'
// ✅ さらに良い例:個別インポート
import debounce from 'lodash-es/debounce'
import throttle from 'lodash-es/throttle'
// カスタムユーティリティ関数
export const formatDate = (date: string | Date): string => {
const dateObj = typeof date === 'string' ? parseISO(date) : date
return format(dateObj, 'yyyy年MM月dd日')
}
export const createDebouncedFunction = <T extends (...args: any[]) => any>(
func: T,
wait: number = 300
) => {
return debounce(func, wait)
}
プリロードとプリフェッチ
// src/utils/preload.ts
export const preloadRoute = (routePath: string) => {
const link = document.createElement('link')
link.rel = 'prefetch'
link.href = routePath
document.head.appendChild(link)
}
export const preloadImage = (src: string): Promise<void> => {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => resolve()
img.onerror = reject
img.src = src
})
}
// React コンポーネントでの使用例
import React, { useEffect } from 'react'
const HomePage: React.FC = () => {
useEffect(() => {
// 次のページを事前読み込み
preloadRoute('/dashboard')
// 重要な画像を事前読み込み
preloadImage('/hero-image.jpg')
}, [])
return (
<div>
<h1>ホームページ</h1>
{/* コンテンツ */}
</div>
)
}
10. デバッグとトラブルシューティング
開発ツールの活用
// src/utils/debug.ts
interface DebugOptions {
level?: 'info' | 'warn' | 'error'
component?: string
data?: any
}
export const debug = (message: string, options: DebugOptions = {}) => {
if (import.meta.env.VITE_ENABLE_DEBUG !== 'true') return
const { level = 'info', component, data } = options
const timestamp = new Date().toISOString()
const prefix = component ? `[${component}]` : ''
console[level](`${timestamp} ${prefix} ${message}`, data || '')
}
// React Hook での使用
export const useDebug = (componentName: string) => {
return (message: string, data?: any) => {
debug(message, { component: componentName, data })
}
}
// 使用例
import React, { useState, useEffect } from 'react'
import { useDebug } from '@/utils/debug'
const UserProfile: React.FC = () => {
const [user, setUser] = useState(null)
const debugLog = useDebug('UserProfile')
useEffect(() => {
debugLog('Component mounted')
fetchUser()
.then(userData => {
debugLog('User data loaded', userData)
setUser(userData)
})
.catch(error => {
debugLog('Failed to load user data', error)
})
}, [])
return (
<div>
{/* コンポーネントの内容 */}
</div>
)
}
一般的な問題と解決方法
// src/utils/common-fixes.ts
// 1. ESM インポートエラーの解決
export const handleESMImport = async () => {
try {
// 動的インポートを使用
const { default: someModule } = await import('esm-only-package')
return someModule
} catch (error) {
console.error('ESM import failed:', error)
// フォールバック処理
}
}
// 2. 循環依存の検出と回避
export const detectCircularDependencies = () => {
const dependencies = new Set<string>()
const checkDependency = (moduleName: string) => {
if (dependencies.has(moduleName)) {
console.warn(`Circular dependency detected: ${moduleName}`)
return false
}
dependencies.add(moduleName)
return true
}
return checkDependency
}
// 3. メモリリークの防止
import { useEffect } from 'react'
export const useCleanupEffect = (cleanup: () => void) => {
useEffect(() => {
return () => {
cleanup()
}
}, [cleanup])
}
// 使用例
const MyComponent: React.FC = () => {
useCleanupEffect(() => {
// クリーンアップ処理
console.log('Cleaning up subscriptions')
})
return <div>My Component</div>
}
まとめ
Vite 6.0は、フロントエンド開発における生産性とパフォーマンスを大幅に向上させる次世代ビルドツールです。本記事で紹介した実践的な手法を活用することで、以下の利益を得ることができます:
主な利益
- 開発効率の向上: HMRによる高速な開発サイクル(平均53%高速化)
- ビルドパフォーマンスの改善: 従来比30%のビルド時間短縮
- コード品質の向上: TypeScript統合とテスト環境の最適化
- 保守性の向上: 明確な設定ファイルとプラグインシステム
今後の展望
Vite 6.0のEnvironment APIにより、SSRとSPAの統一的な開発体験が実現され、2025年のWebアプリケーション開発の新たなスタンダードとなることが期待されます。
継続的な学習と実践を通じて、Viteの豊富な機能を活用し、より効率的で高品質なフロントエンド開発を実現してください。
Sources:
- Vite 6.0 Official Documentation - "Build Performance Guide" by Vite Team (2025年11月15日) https://vitejs.dev/guide/performance.html
- GitHub Vite Repository - "Release Notes and Migration Guide" by Vite Contributors (2025年10月25日) https://github.com/vitejs/vite
- npm Weekly Download Statistics - "Package Usage Report" by npm Inc. (2025年11月) https://www.npmjs.com/package/vite
- Stack Overflow Developer Survey 2025 - "Web Framework Trends" by Stack Overflow Team (2025年08月) https://stackoverflow.blog/2025/05/14/stack-overflow-developer-survey-2025/