vite vs esbuild — どちらを選ぶべきか?徹底比較
1. 結論
アプリケーション開発のフルスタックな開発環境が必要なら vite、ライブラリのバンドルや既存ビルドパイプラインへの高速トランスパイラ組み込みなら esbuild を選んでください。 実は vite は内部で esbuild を利用しており、両者は「競合」というより「レイヤーが異なるツール」です。プロジェクトの要件に応じて、併用するケースも十分にあり得ます。
2. 比較表
| 観点 | vite | esbuild |
|---|---|---|
| カテゴリ | 開発サーバー + ビルドツール(フレームワーク寄り) | バンドラー / トランスパイラ / ミニファイア(低レベル寄り) |
| 言語 | TypeScript + JavaScript(内部で Go 製 esbuild を利用) | Go(ネイティブバイナリ) |
| バンドルサイズ(node_modules) | 約 40〜60 MB(プラグイン込み) | 約 9 MB |
| ビルド速度 | 高速(本番ビルドは Rollup ベース) | 極めて高速(webpack 比で 10〜100 倍) |
| 開発サーバー | ✅ HMR 付きの高機能サーバー内蔵 | ❌ 簡易的な serve モードのみ |
| HMR(Hot Module Replacement) | ✅ フレームワーク対応の高精度 HMR | ❌ 非対応 |
| プラグインシステム | ✅ Rollup 互換プラグイン + Vite 独自フック | ⚠️ プラグイン API はあるが限定的 |
| TypeScript 対応 | ✅ 設定不要でそのまま使える | ✅ ネイティブで高速トランスパイル |
| CSS / PostCSS | ✅ CSS Modules, PostCSS, Sass 等を標準サポート | ⚠️ CSS バンドルは可能だが機能は限定的 |
| フレームワーク対応 | ✅ React / Vue / Svelte / Solid 等の公式テンプレート | ❌ フレームワーク固有の処理は自前実装が必要 |
| Code Splitting | ✅(Rollup ベースで柔軟) | ⚠️ 対応しているが設定の柔軟性は低い |
| Tree Shaking | ✅(Rollup の高精度 Tree Shaking) | ✅(高速だがやや粗い場合あり) |
| 型チェック | ❌(トランスパイルのみ、tsc 別途必要) | ❌(トランスパイルのみ、tsc 別途必要) |
| 学習コスト | 中(設定ファイルは直感的) | 低(API がシンプル) |
| 週間ダウンロード数(2024年時点) | 約 1,500 万 | 約 3,500 万(vite 経由の間接利用含む) |
| ライセンス | MIT | MIT |
3. それぞれの強み
vite の強み
- オールインワンの開発体験:
npm create vite@latest一発で、開発サーバー・HMR・本番ビルド・プレビューサーバーがすべて揃います - フレームワークとの深い統合: React Fast Refresh、Vue SFC、Svelte コンパイルなどがプラグイン一つで動作します
- Native ESM による高速な開発サーバー: 開発時はバンドルせず、ブラウザの ESM をそのまま活用するため、大規模プロジェクトでも起動が高速です
- 豊富なエコシステム: Vitest(テスト)、VitePress(ドキュメント)、Nuxt / SvelteKit / Astro など主要メタフレームワークが Vite ベースです
- 成熟したプラグインエコシステム: Rollup 互換プラグインがそのまま使えるため、選択肢が非常に豊富です
esbuild の強み
- 圧倒的なビルド速度: Go で書かれたネイティブバイナリのため、JavaScript 製ツールとは桁違いの速度を実現します
- シンプルな API: バンドル・トランスパイル・ミニファイという明確な責務に集中しており、設定が最小限です
- 軽量: 依存が少なく、node_modules の肥大化を抑えられます
- 汎用性: CLI・JavaScript API・Go API の 3 通りで利用でき、他ツールへの組み込みが容易です
- ミニファイア単体としての利用: Terser の代替として、既存の webpack / Rollup パイプラインに組み込むだけでビルド時間を大幅短縮できます
4. コード例で比較
タスク: React + TypeScript アプリをビルドする
vite の場合
プロジェクト作成:
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
sourcemap: true,
},
server: {
port: 3000,
open: true,
},
})
src/App.tsx:
import { useState } from 'react'
export default function App() {
const [count, setCount] = useState(0)
return (
<div>
<h1>vite + React + TypeScript</h1>
<button onClick={() => setCount((c) => c + 1)}>
count: {count}
</button>
</div>
)
}
開発 & ビルド:
# 開発サーバー起動(HMR付き)
npm run dev
# 本番ビルド
npm run build
# ビルド結果のプレビュー
npm run preview
esbuild の場合
セットアップ:
mkdir my-app && cd my-app
npm init -y
npm install esbuild react react-dom
npm install -D @types/react @types/react-dom typescript
build.ts(ビルドスクリプト):
import * as esbuild from 'esbuild'
// --- 本番ビルド ---
async function build() {
const result = await esbuild.build({
entryPoints: ['src/index.tsx'],
bundle: true,
outdir: 'dist',
minify: true,
sourcemap: true,
format: 'esm',
splitting: true,
target: ['es2020'],
loader: {
'.tsx': 'tsx',
'.ts': 'ts',
},
define: {
'process.env.NODE_ENV': '"production"',
},
metafile: true,
})
// バンドルサイズの解析
const analysis = await esbuild.analyzeMetafile(result.metafile)
console.log(analysis)
}
build().catch(() => process.exit(1))
watch.ts(開発用の簡易ウォッチ):
import * as esbuild from 'esbuild'
async function watch() {
const ctx = await esbuild.context({
entryPoints: ['src/index.tsx'],
bundle: true,
outdir: 'dist',
format: 'esm',
sourcemap: true,
loader: {
'.tsx': 'tsx',
'.ts': 'ts',
},
define: {
'process.env.NODE_ENV': '"development"',
},
})
// ファイル変更を監視して自動リビルド
await ctx.watch()
// 簡易的な開発サーバー(HMR なし、ライブリロードのみ)
const { host, port } = await ctx.serve({
servedir: 'dist',
})
console.log(`Serving on http://${host}:${port}`)
}
watch().catch(() => process.exit(1))
src/App.tsx(同一コード):
import { useState } from 'react'
export default function App() {
const [count, setCount] = useState(0)
return (
<div>
<h1>esbuild + React + TypeScript</h1>
<button onClick={() => setCount((c) => c + 1)}>
count: {count}
</button>
</div>
)
}
開発 & ビルド:
# 開発(ウォッチ + 簡易サーバー)
npx tsx watch.ts
# 本番ビルド
npx tsx build.ts
比較ポイント
| 観点 | vite | esbuild |
|---|---|---|
| セットアップ行数 | 約 15 行(設定ファイルのみ) | 約 50 行(ビルド + ウォッチスクリプト) |
| HMR | ✅ React Fast Refresh 対応 | ❌ フルリロードのみ |
| HTML 生成 | ✅ 自動(index.html テンプレート) | ❌ 自前で用意が必要 |
| 環境変数 | ✅ .env ファイル対応 | ❌ define で手動設定 |
タスク: ライブラリをバンドルする(esbuild が得意な領域)
esbuild でユーティリティライブラリをビルド:
import * as esbuild from 'esbuild'
// CJS + ESM のデュアルパッケージビルド
async function buildLibrary() {
const shared: esbuild.BuildOptions = {
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: true,
external: ['react', 'react-dom'], // peerDeps は外部化
target: ['es2020'],
}
// ESM 形式
await esbuild.build({
...shared,
format: 'esm',
outfile: 'dist/index.mjs',
})
// CJS 形式
await esbuild.build({
...shared,
format: 'cjs',
outfile: 'dist/index.cjs',
})
console.log('Library build complete!')
}
buildLibrary()
ポイント: ライブラリのビルドでは、esbuild の「速くてシンプル」という特性が最大限に活きます。vite でもライブラリモード(
build.lib)は提供されていますが、内部的には Rollup を使うため、esbuild ほどの速度は出ません。
5. どちらを選ぶべきか — ユースケース別の推奨
✅ vite を選ぶべきケース
| ユースケース | 理由 |
|---|---|
| SPA / MPA のアプリケーション開発 | HMR・開発サーバー・本番ビルドがオールインワン |
| React / Vue / Svelte を使うプロジェクト | フレームワーク統合プラグインが成熟している |
| チーム開発で統一した開発環境が必要 | 設定の規約化がしやすく、エコシステムが豊富 |
| SSR / SSG を含むプロジェクト | Nuxt / SvelteKit / Astro など主要フレームワークが Vite ベース |
| CSS Modules / Sass / PostCSS を多用する | 設定不要で動作する |
✅ esbuild を選ぶべきケース
| ユースケース | 理由 |
|---|---|
| npm パッケージ / ライブラリのビルド | 高速で設定がシンプル、CJS/ESM デュアル出力が容易 |
| 既存ビルドパイプラインの高速化 | ミニファイアやトランスパイラとして部分的に導入可能 |
| CI/CD のビルド時間短縮 | 圧倒的な速度で CI コストを削減 |
| カスタムビルドツールの基盤 | JavaScript API / Go API で柔軟に組み込める |
| シンプルな静的サイトやスクリプトのバンドル | フレームワーク不要な小規模プロジェクト |
✅ 併用するケース(実は最も一般的)
vite を使う = esbuild も使っている
vite は内部で以下の用途に esbuild を利用しています。
- 開発時の依存関係の事前バンドル(Pre-bundling)
- TypeScript / JSX のトランスパイル
- 本番ビルド時の CSS ミニファイ(デフォルト)
- 本番ビルド時の JS ミニファイ(
build.minify: 'esbuild'がデフォルト)
つまり、vite を選んだ時点で esbuild の恩恵は自動的に受けています。
6. まとめ
esbuild = エンジン(速さ・シンプルさ)
vite = 車(エンジン + 快適装備 + フレームワーク統合)
**esb