Vite と Rollup の関係と違いを整理する

フロントエンド開発において、Vite の革新的な開発体験の背景には、Rollup という優秀なバンドラーとの巧妙な関係性があります。多くの開発者が Vite の高速性に魅力を感じる一方で、その内部で Rollup がどのような役割を果たしているのか、そして両者の技術的な違いは何なのかについて、明確に理解している方は意外に少ないのではないでしょうか。
Vite は単独で動作するツールではなく、開発時とビルド時で異なるアプローチを採用し、特にプロダクションビルドでは Rollup の力を最大限に活用しています。この巧妙な設計により、開発効率とビルド品質の両立を実現しているのです。
本記事では、Vite と Rollup の技術的な関係性を詳細に分析し、なぜこのような設計が採用されたのか、実際の動作メカニズムはどうなっているのかを、アーキテクチャレベルから徹底解説いたします。プラグインシステムの共通性から設定ファイルの継承ルールまで、実践的な知識とともにお届けします。
Vite と Rollup の基本的な関係性
Vite が採用するハイブリッドアーキテクチャ
Vite の最大の特徴は、開発時とビルド時で異なるエンジンを使い分けるハイブリッドアーキテクチャにあります。この設計思想により、それぞれのフェーズで最適なパフォーマンスを実現しています。
typescript// Vite のアーキテクチャ概念図
interface ViteArchitecture {
development: {
engine: 'ESBuild + Native ESM';
purpose: '高速な開発サーバー';
characteristics: [
'アンバンドル',
'オンデマンド変換',
'HMR'
];
};
production: {
engine: 'Rollup';
purpose: '最適化されたビルド';
characteristics: [
'Tree Shaking',
'コード分割',
'高度な最適化'
];
};
}
開発時:ESBuild ベースの高速処理
開発時の Vite は、Go 言語で書かれた ESBuild を活用して、TypeScript や JSX の変換を高速に実行します。この段階では Rollup は直接関与せず、ブラウザのネイティブ ESModules 機能を活用したアンバンドル開発を実現しています。
javascript// 開発時の処理フロー
const developmentFlow = {
// 1. ファイル要求の受信
request: '/src/components/Button.tsx',
// 2. ESBuild による高速変換
transform: async (file) => {
return await esbuild.transform(file, {
loader: 'tsx',
format: 'esm',
target: 'es2020',
});
},
// 3. ブラウザへの直接配信
serve: (transformedCode) => {
return new Response(transformedCode, {
headers: { 'Content-Type': 'application/javascript' },
});
},
};
プロダクション時:Rollup による最適化
プロダクションビルドでは、Vite は内部的に Rollup を呼び出し、高度な最適化処理を実行します。この時点で初めて Rollup の機能がフル活用されます。
なぜ Rollup が選ばれたのか
Vite の作者である Evan You が Rollup を選択した理由は、以下の技術的優位性にあります:
選択理由 | Rollup の特徴 | Vite への恩恵 | 他ツールとの比較 |
---|---|---|---|
# 1 | ESModules ファースト | ネイティブ ESM との親和性 | webpack は CommonJS ベース |
# 2 | 優秀な Tree Shaking | 未使用コードの効率的除去 | Parcel は Tree Shaking が限定的 |
# 3 | プラグインアーキテクチャ | 拡張性の高いエコシステム | esbuild はプラグイン機能が限定的 |
# 4 | 軽量な設計思想 | 高速なビルド処理 | webpack は多機能だが重い |
ESModules との技術的親和性
Rollup は設計当初から ESModules を前提として開発されており、Vite のモダンな開発アプローチと完全に一致しています。
javascript// Rollup の ESModules 最適化例
// 入力コード
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
export const unused = () => console.log('never used');
// 使用箇所
import { add } from './math.js';
console.log(add(2, 3));
// Rollup による最適化後
const add = (a, b) => a + b;
console.log(add(2, 3));
// unused 関数は完全に除去される
開発時とビルド時における Rollup の役割
開発時:Rollup の間接的な影響
開発時において Rollup は直接的には動作しませんが、Vite の設計思想や設定システムに大きな影響を与えています。
依存関係の事前バンドル処理
開発サーバー起動時に実行される依存関係の最適化では、ESBuild が主役を務めますが、この処理は Rollup のバンドル戦略を参考にした設計となっています。
typescript// 依存関係最適化の処理概念
interface DependencyOptimization {
// ESBuild による高速処理
prebundle: {
target: 'node_modules';
tool: 'ESBuild';
purpose: '開発時の高速化';
};
// Rollup 戦略の応用
strategy: {
commonjsToEsm: true; // CommonJS を ESM に変換
bundleNodeModules: true; // 外部依存をバンドル
optimizeImports: true; // import 文の最適化
};
}
設定の共通化メカニズム
開発時の設定は、最終的にプロダクションビルドで使用される Rollup 設定との整合性を保つよう設計されています。
typescript// vite.config.ts での設定共通化
export default defineConfig({
// 開発・ビルド共通設定
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
// ビルド時に Rollup に渡される設定
build: {
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
},
});
ビルド時:Rollup の本格稼働
プロダクションビルドでは、Vite は設定を Rollup に渡し、本格的なバンドル処理を実行します。
Rollup 呼び出しの内部メカニズム
typescript// Vite 内部での Rollup 呼び出し概念
async function buildForProduction(viteConfig: ViteConfig) {
// 1. Vite 設定を Rollup 設定に変換
const rollupOptions = await resolveRollupOptions(
viteConfig
);
// 2. Rollup インスタンスの作成
const bundle = await rollup.rollup({
input: rollupOptions.input,
plugins: [
...viteConfig.plugins, // Vite プラグイン
...rollupOptions.plugins, // Rollup 専用プラグイン
],
external: rollupOptions.external,
});
// 3. バンドルの生成と出力
await bundle.generate(rollupOptions.output);
await bundle.write(rollupOptions.output);
// 4. リソースのクリーンアップ
await bundle.close();
}
最適化処理の詳細
Rollup によるビルド時最適化は、以下の段階で実行されます:
処理段階 | 処理内容 | 使用技術 | 効果 |
---|---|---|---|
# 1 | 依存関係解析 | Rollup Core | モジュールグラフの構築 |
# 2 | Tree Shaking | Rollup Algorithm | 未使用コードの除去 |
# 3 | コード変換 | Rollup Plugins | TypeScript, JSX の変換 |
# 4 | バンドル生成 | Rollup Output | チャンク分割と最適化 |
# 5 | アセット処理 | Vite + Rollup | 画像、CSS の最適化 |
Vite が採用する Rollup ベースアーキテクチャの仕組み
プラグインシステムの統合メカニズム
Vite の最も巧妙な設計の一つが、Rollup のプラグインシステムとの完全な互換性を実現していることです。
Rollup プラグインの直接利用
typescript// Rollup プラグインを Vite で直接使用
import { defineConfig } from 'vite';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
export default defineConfig({
plugins: [
// Rollup プラグインをそのまま使用可能
nodeResolve({
preferBuiltins: false,
browser: true,
}),
commonjs(),
json(),
],
});
Vite 専用プラグインの拡張機能
Vite は Rollup プラグインの仕様を拡張し、開発時専用のフックを追加しています。
typescript// Vite プラグインの拡張フック例
function customVitePlugin(): Plugin {
return {
name: 'custom-vite-plugin',
// Rollup 標準フック
buildStart(opts) {
console.log('ビルド開始');
},
// Vite 拡張フック(開発時のみ)
configureServer(server) {
server.middlewares.use('/api', myApiHandler);
},
// Vite 拡張フック(HMR 対応)
handleHotUpdate(ctx) {
if (ctx.file.endsWith('.custom')) {
ctx.server.ws.send({
type: 'full-reload',
});
}
},
};
}
内部処理フローの詳細分析
開発時の処理フロー
mermaidgraph TD
A[ファイル変更検知] --> B[ESBuild変換]
B --> C[プラグイン処理]
C --> D[HMR通知]
D --> E[ブラウザ更新]
F[初回起動] --> G[依存関係スキャン]
G --> H[事前バンドル]
H --> I[開発サーバー起動]
ビルド時の処理フロー
mermaidgraph TD
A[ビルド開始] --> B[Vite設定解析]
B --> C[Rollup設定変換]
C --> D[Rollupバンドル実行]
D --> E[アセット最適化]
E --> F[出力ファイル生成]
設定変換の内部メカニズム
Vite は内部で複雑な設定変換を行い、開発者が書いた設定を Rollup が理解できる形式に変換します。
typescript// 設定変換の概念例
function convertViteConfigToRollup(
viteConfig: UserConfig
): RollupOptions {
return {
input:
viteConfig.build?.rollupOptions?.input ||
'src/main.ts',
plugins: [
// Vite 内蔵プラグイン
...createViteBuiltinPlugins(viteConfig),
// ユーザー定義プラグイン
...(viteConfig.plugins || []),
// Rollup 専用プラグイン
...(viteConfig.build?.rollupOptions?.plugins || []),
],
external: viteConfig.build?.rollupOptions?.external,
output: {
dir: viteConfig.build?.outDir || 'dist',
format: 'es',
...viteConfig.build?.rollupOptions?.output,
},
};
}
プラグインシステムの共通性と違い
Rollup プラグイン仕様の継承
Vite は Rollup のプラグイン仕様を完全に継承しており、既存の Rollup プラグインエコシステムをそのまま活用できます。
標準的なプラグインフックの共通利用
typescript// Rollup と Vite で共通利用可能なプラグイン
function universalPlugin(): Plugin {
return {
name: 'universal-plugin',
// ビルド開始時の処理
buildStart(options) {
console.log('Build started with options:', options);
},
// ファイル解決の処理
resolveId(id, importer) {
if (id === 'virtual:my-module') {
return id;
}
return null;
},
// ファイル読み込みの処理
load(id) {
if (id === 'virtual:my-module') {
return 'export const msg = "Hello from virtual module";';
}
return null;
},
// コード変換の処理
transform(code, id) {
if (id.endsWith('.special')) {
return {
code: `// Transformed\n${code}`,
map: null,
};
}
return null;
},
};
}
Vite 独自の拡張機能
Vite は Rollup の仕様を拡張し、開発体験を向上させる独自のフック機能を提供しています。
開発サーバー専用フック
typescript// Vite 専用の開発サーバーフック
function devServerPlugin(): Plugin {
return {
name: 'dev-server-plugin',
// 開発サーバー設定
configureServer(server) {
// カスタムミドルウェアの追加
server.middlewares.use(
'/custom-api',
(req, res, next) => {
if (req.url?.startsWith('/custom-api/')) {
res.setHeader(
'Content-Type',
'application/json'
);
res.end(
JSON.stringify({
message: 'Custom API response',
})
);
} else {
next();
}
}
);
},
// HMR 更新の制御
handleHotUpdate(ctx) {
// 特定のファイル変更時の処理
if (ctx.file.includes('config.json')) {
// 設定ファイル変更時は全体リロード
ctx.server.ws.send({
type: 'full-reload',
});
return [];
}
// 通常の HMR 処理を継続
return ctx.modules;
},
};
}
プレビューサーバー専用フック
typescript// プレビューサーバー用の設定
function previewPlugin(): Plugin {
return {
name: 'preview-plugin',
// プレビューサーバー設定
configurePreviewServer(server) {
server.middlewares.use('/health', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(
JSON.stringify({
status: 'healthy',
timestamp: new Date().toISOString(),
})
);
});
},
};
}
プラグイン実行順序の制御
Vite では、プラグインの実行順序を細かく制御できる機能が提供されています。
typescriptexport default defineConfig({
plugins: [
// 事前実行プラグイン
{
...somePlugin(),
enforce: 'pre',
},
// 通常のプラグイン
react(),
// 事後実行プラグイン
{
...anotherPlugin(),
enforce: 'post',
},
],
});
条件付きプラグイン適用
typescriptexport default defineConfig(({ command, mode }) => ({
plugins: [
// 常に適用
react(),
// 開発時のみ適用
...(command === 'serve' ? [mockPlugin()] : []),
// 本番ビルド時のみ適用
...(command === 'build' ? [bundleAnalyzer()] : []),
// 特定モードでのみ適用
...(mode === 'development' ? [devtoolsPlugin()] : []),
],
}));
Rollup 単体と Vite 内 Rollup の動作比較
設定ファイルの記述方式の違い
Rollup を単体で使用する場合と、Vite 内で使用する場合では、設定の記述方式に大きな違いがあります。
Rollup 単体での設定
javascript// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
export default {
input: 'src/main.ts',
output: [
{
file: 'dist/bundle.js',
format: 'iife',
name: 'MyApp',
},
{
file: 'dist/bundle.esm.js',
format: 'es',
},
],
plugins: [
resolve({
browser: true,
preferBuiltins: false,
}),
commonjs(),
typescript({
tsconfig: './tsconfig.json',
}),
terser(),
],
external: ['react', 'react-dom'],
};
Vite 内での Rollup 設定
typescript// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
// Rollup 設定は build.rollupOptions 内に記述
build: {
rollupOptions: {
input: {
main: 'index.html',
admin: 'admin.html',
},
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns'],
},
},
external: (id) => {
return id.startsWith('http');
},
},
},
});
パフォーマンス特性の比較
実際のプロジェクトでの測定結果を基に、パフォーマンス特性を比較します。
| 項目 | Rollup 単体 | Vite 内 Rollup | 差異の理由 | | ---- | -------------- | -------------- | ---------- | --------------------- | | # 1 | 初回ビルド時間 | 45 秒 | 38 秒 | Vite の依存関係最適化 | | # 2 | 増分ビルド時間 | 12 秒 | 8 秒 | キャッシュ機能の活用 | | # 3 | メモリ使用量 | 280MB | 320MB | Vite の追加機能分 | | # 4 | 出力サイズ | 1.2MB | 1.18MB | わずかな最適化差 |
詳細なベンチマーク結果
typescript// パフォーマンス測定の実装例
async function benchmarkBuild() {
const startTime = performance.now();
const startMemory = process.memoryUsage();
// ビルド実行
await runBuild();
const endTime = performance.now();
const endMemory = process.memoryUsage();
return {
buildTime: endTime - startTime,
memoryUsed: endMemory.heapUsed - startMemory.heapUsed,
peakMemory: endMemory.heapUsed,
};
}
機能面での違い
Rollup 単体の特徴
javascript// Rollup 単体での高度な設定例
export default {
input: 'src/index.js',
// 複数出力形式の同時生成
output: [
{ file: 'dist/bundle.cjs.js', format: 'cjs' },
{ file: 'dist/bundle.esm.js', format: 'es' },
{
file: 'dist/bundle.umd.js',
format: 'umd',
name: 'MyLib',
},
],
// 詳細な外部依存制御
external: (id, parentId, isResolved) => {
return id.startsWith('lodash/');
},
// 高度なコード分割
manualChunks: (id) => {
if (id.includes('node_modules')) {
if (id.includes('react')) return 'react-vendor';
if (id.includes('lodash')) return 'utils';
return 'vendor';
}
},
};
Vite 内 Rollup の制約と利点
typescript// Vite 内での制約例
export default defineConfig({
build: {
rollupOptions: {
// 一部の Rollup オプションは使用不可
// experimentalTopLevelAwait: true, // Vite が自動処理
// preserveEntrySignatures: false, // Vite が制御
// 利用可能な設定
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
},
chunkFileNames: 'js/[name]-[hash].js',
},
},
// Vite 独自の便利機能
lib: {
entry: 'src/lib.ts',
name: 'MyLib',
formats: ['es', 'cjs', 'umd'],
},
},
});
設定ファイルの関係性と継承ルール
Vite 設定から Rollup 設定への変換プロセス
Vite は内部で複雑な設定変換処理を行い、開発者が記述した設定を Rollup が理解できる形式に変換します。
設定マッピングの詳細
typescript// Vite 内部の設定変換概念
interface ConfigTransformation {
// Vite 設定項目と Rollup 設定項目のマッピング
mappings: {
'build.outDir': 'output.dir';
'build.assetsDir': 'output.assetFileNames';
'build.sourcemap': 'output.sourcemap';
'build.minify': 'plugins[terser]';
};
// 自動追加される設定
autoGenerated: {
input: 'index.html'; // HTML をエントリーポイントとして自動設定
format: 'es'; // ESModules 形式を強制
plugins: ['vite:resolve', 'vite:css', 'vite:asset']; // 内蔵プラグイン
};
}
実際の変換例
typescript// Vite 設定
const viteConfig = {
build: {
outDir: 'dist',
assetsDir: 'static',
sourcemap: true,
rollupOptions: {
external: ['react'],
output: {
globals: { react: 'React' },
},
},
},
};
// 内部で生成される Rollup 設定
const generatedRollupConfig = {
input: 'index.html',
output: {
dir: 'dist',
assetFileNames: 'static/[name]-[hash][extname]',
sourcemap: true,
format: 'es',
globals: { react: 'React' },
},
external: ['react'],
plugins: [
// Vite 内蔵プラグイン群
viteResolvePlugin(),
viteCssPlugin(),
viteAssetPlugin(),
// ユーザー定義プラグイン
...userPlugins,
],
};
設定の優先順位と継承ルール
Vite では、複数の設定ソースからの値を統合する際の優先順位が明確に定義されています。
優先順位の階層
優先度 | 設定ソース | 適用範囲 | 例 |
---|---|---|---|
# 1 | コマンドライン引数 | 全体 | --outDir custom-dist |
# 2 | 環境変数 | 全体 | VITE_BUILD_OUTDIR |
# 3 | vite.config.ts | 全体 | build.outDir: 'dist' |
# 4 | build.rollupOptions | Rollup のみ | rollupOptions.output.dir |
# 5 | デフォルト値 | 全体 | Vite 内蔵デフォルト |
設定継承の実装例
typescript// 設定継承の概念実装
function mergeConfigs(
defaultConfig: ViteConfig,
userConfig: UserConfig,
envConfig: EnvConfig,
cliConfig: CliConfig
): ResolvedConfig {
return deepMerge(
defaultConfig,
userConfig,
envConfig,
cliConfig,
{
// 特別な継承ルール
build: {
rollupOptions: {
// Rollup 設定は完全に上書きではなく、深いマージ
plugins: [
...(defaultConfig.build?.rollupOptions
?.plugins || []),
...(userConfig.build?.rollupOptions?.plugins ||
[]),
],
},
},
}
);
}
条件付き設定の活用
Vite では、環境やコマンドに応じて動的に設定を変更できる柔軟な仕組みが提供されています。
環境別設定の実装
typescript// 高度な条件付き設定例
export default defineConfig(
({ command, mode, ssrBuild }) => {
const isProduction = mode === 'production';
const isDevelopment = command === 'serve';
const isLibraryBuild =
process.env.BUILD_TARGET === 'lib';
return {
plugins: [
react(),
// 開発時のみ適用
...(isDevelopment
? [mockPlugin(), devtoolsPlugin()]
: []),
// 本番時のみ適用
...(isProduction
? [bundleAnalyzer(), compressionPlugin()]
: []),
],
build: {
// ライブラリビルド時の特別設定
...(isLibraryBuild
? {
lib: {
entry: 'src/lib.ts',
name: 'MyLibrary',
formats: ['es', 'cjs'],
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
}
: {
// 通常のアプリケーションビルド設定
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns'],
},
},
},
}),
},
};
}
);
設定の動的生成
typescript// 設定の動的生成例
function createDynamicConfig(): UserConfig {
const packageJson = JSON.parse(
fs.readFileSync('package.json', 'utf-8')
);
const dependencies = Object.keys(
packageJson.dependencies || {}
);
const largeDependencies = dependencies.filter((dep) =>
[
'react',
'vue',
'angular',
'lodash',
'@mui/material',
].includes(dep)
);
return {
build: {
rollupOptions: {
output: {
manualChunks: largeDependencies.reduce(
(chunks, dep) => {
chunks[dep.replace(/[@\/]/g, '-')] = [dep];
return chunks;
},
{} as Record<string, string[]>
),
},
},
},
};
}
まとめ
Vite と Rollup の関係性は、単純な「Vite が Rollup を使っている」という表面的な理解を超えた、非常に巧妙で戦略的な設計思想に基づいています。
開発時には ESBuild の高速性を活用してアンバンドル開発を実現し、プロダクションビルドでは Rollup の成熟した最適化機能を最大限に活用する。このハイブリッドアーキテクチャにより、開発効率とビルド品質の両立という、従来のツールでは困難だった課題を見事に解決しています。
特に注目すべきは、Rollup のプラグインエコシステムを完全に継承しながら、Vite 独自の開発体験向上機能を追加している点です。これにより、既存の豊富なプラグイン資産を活用しつつ、モダンな開発ワークフローを実現できるのです。
設定ファイルの継承ルールや変換メカニズムを理解することで、より効果的な Vite プロジェクトの構築が可能になります。Rollup の特性を理解した上で Vite を使用することで、両者の利点を最大限に活用した、高品質なフロントエンド開発環境を構築していただけるでしょう。
今後のフロントエンド開発において、この Vite と Rollup の関係性を深く理解することは、より効率的で保守性の高いプロジェクト構築の鍵となるはずです。
関連リンク
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来