Vite プラグインフック対応表:Rollup → Vite マッピング早見表
Vite でプラグイン開発を行う際、Rollup のプラグインフックがそのまま使えることは大きなメリットです。しかし、Vite 独自のフックや、Rollup フックの拡張機能について理解していないと、せっかくの機能を活かしきれません。
本記事では、Rollup から Vite へのプラグインフックマッピングを体系的に整理し、どのフックがどのタイミングで実行されるのか、Vite ではどのような拡張が加えられているのかを明確にします。これにより、既存の Rollup プラグインを Vite に移行する際や、Vite プラグインを新規開発する際の指針となるでしょう。
プラグインフック早見表
ビルド時フック対応表
| # | Rollup フック | Vite での対応 | 実行タイミング | 用途 |
|---|---|---|---|---|
| 1 | options | ✓ 対応 | ビルド開始前 | Rollup オプションの変更 |
| 2 | buildStart | ✓ 対応 | ビルド開始時 | 初期化処理・グローバル状態の準備 |
| 3 | resolveId | ✓ 対応(拡張) | モジュール解決時 | カスタムモジュール解決ロジック |
| 4 | load | ✓ 対応(拡張) | ファイル読み込み時 | カスタムローダー実装 |
| 5 | transform | ✓ 対応(拡張) | コード変換時 | コードトランスパイル・変換処理 |
| 6 | buildEnd | ✓ 対応 | ビルド終了時 | クリーンアップ・統計情報出力 |
| 7 | closeBundle | ✓ 対応 | バンドル完了後 | 最終処理・リソース解放 |
出力生成フック対応表
| # | Rollup フック | Vite での対応 | 実行タイミング | 用途 |
|---|---|---|---|---|
| 1 | outputOptions | ✓ 対応 | 出力前 | 出力オプションの変更 |
| 2 | renderStart | ✓ 対応 | レンダリング開始時 | 出力準備処理 |
| 3 | banner | ✓ 対応 | バンドル生成時 | ファイル先頭へのバナー追加 |
| 4 | footer | ✓ 対応 | バンドル生成時 | ファイル末尾へのフッター追加 |
| 5 | intro | ✓ 対応 | バンドル生成時 | ラッパー内部の先頭コード追加 |
| 6 | outro | ✓ 対応 | バンドル生成時 | ラッパー内部の末尾コード追加 |
| 7 | renderChunk | ✓ 対応 | チャンク生成時 | 各チャンクの変換処理 |
| 8 | augmentChunkHash | ✓ 対応 | ハッシュ生成時 | チャンクハッシュのカスタマイズ |
| 9 | generateBundle | ✓ 対応 | バンドル生成後 | アセット追加・バンドル変更 |
| 10 | writeBundle | ✓ 対応 | ファイル書き込み後 | 書き込み完了後の処理 |
Vite 専用フック一覧
| # | Vite フック | 実行タイミング | 用途 |
|---|---|---|---|
| 1 | config | 設定解決前 | Vite 設定の変更 |
| 2 | configResolved | 設定解決後 | 解決済み設定の参照 |
| 3 | configureServer | dev サーバー構築時 | ミドルウェア追加・サーバーカスタマイズ |
| 4 | configurePreviewServer | preview サーバー構築時 | プレビューサーバーのカスタマイズ |
| 5 | transformIndexHtml | HTML 変換時 | HTML の変換・タグ注入 |
| 6 | handleHotUpdate | HMR 更新時 | HMR 動作のカスタマイズ |
フック実行順序対応表
| # | フェーズ | Rollup/Vite 共通フック | Vite 専用フック | 備考 |
|---|---|---|---|---|
| 1 | 設定 | - | config → configResolved | Vite のみ |
| 2 | サーバー起動 | - | configureServer / configurePreviewServer | dev/preview 時のみ |
| 3 | ビルド開始 | options → buildStart | - | - |
| 4 | モジュール処理 | resolveId → load → transform | - | 各モジュールごとに繰り返し |
| 5 | HTML 処理 | - | transformIndexHtml | Vite のみ |
| 6 | ビルド終了 | buildEnd | - | - |
| 7 | 出力生成 | outputOptions → renderStart | - | - |
| 8 | チャンク処理 | banner/footer/intro/outro → renderChunk | - | 各チャンクごとに実行 |
| 9 | バンドル生成 | augmentChunkHash → generateBundle → writeBundle | - | - |
| 10 | 完了 | closeBundle | - | - |
背景
Vite と Rollup の関係性
Vite は本番ビルドに Rollup を使用しており、プラグインシステムも Rollup のものをベースに設計されています。
mermaidflowchart TB
vite["Vite プラグインシステム"]
rollup["Rollup プラグインシステム"]
original["Vite 独自フック"]
extended["拡張された<br/>Rollup フック"]
vite --> rollup
vite --> original
rollup --> extended
rollup -.->|"options<br/>buildStart<br/>resolveId<br/>load<br/>など"| base["基本フック群"]
original -.->|"config<br/>configureServer<br/>transformIndexHtml<br/>など"| viteonly["Vite 専用機能"]
extended -.->|"ssr パラメータ<br/>追加プロパティ"| enhance["機能拡張"]
この図が示すように、Vite は Rollup のプラグイン仕様を継承しつつ、開発サーバーや HMR といった Vite 特有の機能に対応するための独自フックを追加しています。
Rollup プラグインの互換性
Vite は Rollup プラグインとの高い互換性を持ちますが、完全な互換性があるわけではありません。
互換性がある部分:
- ビルド時のコアフック(
resolveId、load、transformなど) - 出力生成フック(
generateBundle、writeBundleなど) - フックの実行順序とライフサイクル
互換性に注意が必要な部分:
- 開発時と本番ビルド時で動作が異なるフック
- Vite が拡張したパラメータや戻り値
- SSR 固有の動作
多くの Rollup プラグインは Vite でそのまま動作しますが、開発サーバー特有の最適化を活用するには Vite 専用フックの理解が必要です。
課題
Rollup プラグインを Vite で使う際の混乱ポイント
Rollup プラグインを Vite に移行する際、以下のような課題に直面することがあります。
1. フックの実行タイミングの違い
開発時(dev サーバー)と本番ビルド時で、同じフックでも実行されるタイミングや回数が異なる場合があります。
typescriptexport default function myPlugin() {
return {
name: 'my-plugin',
// このフックは開発時には各リクエストごとに実行される
// 本番ビルド時には全モジュールに対して一度だけ実行される
transform(code, id) {
console.log('transform called for:', id);
return code;
},
};
}
2. Vite 拡張パラメータの見落とし
Vite は Rollup のフックにパラメータを追加していますが、これを見落とすと機能を十分に活用できません。
typescript// Rollup での resolveId
resolveId(source: string, importer: string | undefined)
// Vite での resolveId(SSR パラメータが追加されている)
resolveId(
source: string,
importer: string | undefined,
options: { ssr?: boolean } // Vite が追加
)
3. 開発サーバー機能への対応不足
Rollup プラグインをそのまま使うと、HMR や開発サーバーのミドルウェアといった Vite の開発体験を向上させる機能が利用できません。
以下の図は、Rollup プラグインと Vite プラグインでカバーできる機能範囲の違いを示しています。
mermaidflowchart LR
subgraph rollup["Rollup プラグインでカバー可能"]
build["本番ビルド処理"]
bundle["バンドル生成"]
transform_r["コード変換"]
end
subgraph vite["Vite プラグイン独自機能"]
dev["開発サーバー"]
hmr["HMR 制御"]
html["HTML 変換"]
middleware["ミドルウェア"]
end
rollup -.->|"そのまま移植"| partial["部分的な機能のみ"]
vite -.->|"専用フック追加"| full["完全な Vite 体験"]
解決策
フック対応マッピングの理解
Vite で効果的なプラグイン開発を行うには、各フックの対応関係と特性を理解することが重要です。
ビルド時フックの活用
Rollup の基本的なビルドフックは、Vite でもほぼそのまま使用できます。
options フック
typescriptimport type { Plugin } from 'vite';
export default function myPlugin(): Plugin {
return {
name: 'my-plugin',
// Rollup オプションを変更する
options(opts) {
return {
...opts,
// 外部依存関係を追加
external: [
...(opts.external || []),
'my-external-lib',
],
};
},
};
}
このフックは、ビルドプロセスが開始される前に Rollup の設定を動的に変更できます。外部ライブラリの指定や、入力オプションの調整に使用されます。
buildStart フック
typescriptexport default function myPlugin(): Plugin {
return {
name: 'my-plugin',
// ビルド開始時の初期化処理
buildStart(options) {
console.log('ビルドを開始します');
// グローバル状態の初期化
// キャッシュのクリア
// 統計情報の準備
},
};
}
ビルドの開始時に一度だけ実行され、プラグインの初期化処理に最適です。
モジュール処理フックの拡張
Vite は resolveId、load、transform といったモジュール処理フックに対して、SSR や開発サーバー特有のパラメータを追加しています。
resolveId フック(Vite 拡張版)
typescriptexport default function myPlugin(): Plugin {
return {
name: 'my-plugin',
resolveId(source, importer, options) {
// Vite が追加した SSR フラグを活用
if (options.ssr) {
// SSR ビルド時の特別な解決ロジック
if (source === 'client-only-lib') {
return {
id: 'ssr-alternative-lib',
external: true,
};
}
}
// カスタムプロトコルの処理
if (source.startsWith('virtual:')) {
return source;
}
return null; // 他のプラグインや標準解決に委ねる
},
};
}
resolveId は、モジュールのインポートパスを解決する際に呼び出されます。仮想モジュールの実装や、SSR 時の条件分岐に活用できます。
load フック(Vite 拡張版)
typescriptexport default function myPlugin(): Plugin {
return {
name: 'my-plugin',
load(id, options) {
// 仮想モジュールのコンテンツを返す
if (id === 'virtual:my-module') {
return {
code: `export const message = "Hello from virtual module";`,
map: null, // ソースマップ
};
}
// SSR 時の特別な処理
if (options?.ssr && id.endsWith('.client.ts')) {
return {
code: `export default {};`, // クライアント専用モジュールを空にする
};
}
return null;
},
};
}
load フックは、実際のファイルシステムからではなく、プラグインが生成したコードを返すことができます。仮想モジュールの実装に必須のフックです。
transform フック(Vite 拡張版)
typescriptexport default function myPlugin(): Plugin {
return {
name: 'my-plugin',
transform(code, id, options) {
// 特定の拡張子のファイルのみ処理
if (!id.endsWith('.custom')) {
return null;
}
// SSR フラグによる条件分岐
const isSSR = options?.ssr === true;
const transformedCode = code
.replace(/CLIENT_SIDE/g, isSSR ? 'false' : 'true')
.replace(/SERVER_SIDE/g, isSSR ? 'true' : 'false');
return {
code: transformedCode,
map: null, // ソースマップを返すことも可能
};
},
};
}
transform は最も頻繁に使用されるフックで、コードの変換処理を行います。トランスパイラやプリプロセッサの実装に使われます。
ビルド終了フック
buildEnd フック
typescriptexport default function myPlugin(): Plugin {
return {
name: 'my-plugin',
buildEnd(error) {
if (error) {
console.error('ビルドエラーが発生しました:', error);
// エラー処理
} else {
console.log('ビルドが正常に完了しました');
// 統計情報の出力
// 一時ファイルのクリーンアップ
}
},
};
}
ビルドプロセスの終了時に呼び出され、エラーハンドリングやクリーンアップに使用されます。
closeBundle フック
typescriptexport default function myPlugin(): Plugin {
return {
name: 'my-plugin',
closeBundle() {
// すべてのバンドル処理が完了した後に実行
console.log('すべてのバンドル処理が完了しました');
// リソースの解放
// 最終レポートの生成
},
};
}
すべての出力ファイルが書き込まれた後に実行されます。最終的なクリーンアップ処理に適しています。
出力生成フックの活用
出力生成フェーズでは、生成されるバンドルファイルを直接操作できます。
generateBundle フック
typescriptexport default function myPlugin(): Plugin {
return {
name: 'my-plugin',
generateBundle(options, bundle) {
// バンドルに新しいアセットを追加
this.emitFile({
type: 'asset',
fileName: 'my-asset.txt',
source: 'This is my custom asset',
});
// 既存のチャンクを変更
for (const fileName in bundle) {
const chunk = bundle[fileName];
if (chunk.type === 'chunk') {
// コードの末尾にコメントを追加
chunk.code += '\n// Generated by my-plugin';
}
}
},
};
}
このフックでは、生成されたバンドルに新しいファイルを追加したり、既存のファイルを変更したりできます。
renderChunk フック
typescriptexport default function myPlugin(): Plugin {
return {
name: 'my-plugin',
renderChunk(code, chunk, options) {
// 各チャンクごとに実行される
console.log(`Processing chunk: ${chunk.fileName}`);
// コードの最適化や変換
const optimizedCode = code.replace(
/console\.log/g,
'// console.log'
);
return {
code: optimizedCode,
map: null,
};
},
};
}
各チャンクが生成されるたびに実行され、チャンク単位でのコード変換が可能です。本番ビルドでの最適化処理に使用されます。
Vite 専用フックの活用
Vite 独自のフックを活用することで、開発体験を大幅に向上させることができます。
config フック
typescriptimport type { Plugin, UserConfig } from 'vite';
export default function myPlugin(): Plugin {
return {
name: 'my-plugin',
// Vite 設定を変更
config(config, { command, mode }) {
// 開発時のみ適用する設定
if (command === 'serve') {
return {
server: {
port: 3000,
},
};
}
// 本番ビルド時の設定
if (command === 'build') {
return {
build: {
minify: 'terser',
},
};
}
},
};
}
config フックは、ユーザーの設定と統合される前に Vite の設定を変更できます。プラグインが特定の設定を必要とする場合に使用します。
configResolved フック
typescriptexport default function myPlugin(): Plugin {
let resolvedConfig: ResolvedConfig;
return {
name: 'my-plugin',
// 解決済みの設定を保存
configResolved(config) {
resolvedConfig = config;
console.log('最終的な設定:', config);
// 他のフックで使用するために設定を保存
},
transform(code, id) {
// 保存した設定を活用
if (resolvedConfig.command === 'serve') {
// 開発時の処理
}
return code;
},
};
}
すべての設定が解決された後に呼び出され、最終的な設定を参照できます。他のフックで設定情報を使用する際に便利です。
configureServer フック
typescriptimport type { Plugin, ViteDevServer } from 'vite';
export default function myPlugin(): Plugin {
return {
name: 'my-plugin',
configureServer(server: ViteDevServer) {
// カスタムミドルウェアの追加
server.middlewares.use((req, res, next) => {
if (req.url === '/api/custom') {
res.end('Custom API response');
return;
}
next();
});
// サーバーインスタンスを保存して後で使用
// HMR の手動トリガーなどに活用
},
};
}
開発サーバーが起動する際に呼び出され、カスタムミドルウェアの追加や、サーバーインスタンスへのアクセスが可能です。
transformIndexHtml フック
typescriptexport default function myPlugin(): Plugin {
return {
name: 'my-plugin',
transformIndexHtml(html) {
// HTML にタグを注入
return html.replace(
'</head>',
` <script src="/my-injected-script.js"></script>\n</head>`
);
},
};
}
より高度な HTML 変換も可能です。
typescriptexport default function myPlugin(): Plugin {
return {
name: 'my-plugin',
transformIndexHtml: {
// 実行順序を指定(pre または post)
order: 'pre',
handler(html, ctx) {
// コンテキスト情報を活用
console.log('処理中のファイル:', ctx.filename);
console.log('バンドル情報:', ctx.bundle);
// タグ形式で注入することも可能
return {
html,
tags: [
{
tag: 'meta',
attrs: {
name: 'description',
content: 'My app',
},
injectTo: 'head',
},
{
tag: 'script',
attrs: {
src: '/my-script.js',
type: 'module',
},
injectTo: 'body',
},
],
};
},
},
};
}
transformIndexHtml は、index.html に対して変換処理を行います。メタタグの追加やスクリプトの注入に使用されます。
handleHotUpdate フック
typescriptimport type { Plugin, HmrContext } from 'vite';
export default function myPlugin(): Plugin {
return {
name: 'my-plugin',
handleHotUpdate(ctx: HmrContext) {
// 特定のファイル変更時の HMR 動作をカスタマイズ
if (ctx.file.endsWith('.custom')) {
console.log(
'カスタムファイルが更新されました:',
ctx.file
);
// 影響を受けるモジュールをフィルタリング
const affectedModules = ctx.modules.filter(
(mod) => {
// 条件に基づいてモジュールを選択
return mod.id?.includes('specific-path');
}
);
// カスタムイベントをクライアントに送信
ctx.server.ws.send({
type: 'custom',
event: 'custom-update',
data: { file: ctx.file },
});
// HMR を適用するモジュールを返す
return affectedModules;
}
},
};
}
ファイルが変更された際の HMR の動作をカスタマイズできます。特定のファイルタイプに対する HMR の最適化や、カスタムイベントの送信に使用されます。
具体例
実践的なプラグイン開発例
ここでは、Rollup フックと Vite 専用フックを組み合わせた実践的なプラグインの例を紹介します。
環境変数注入プラグイン
開発時と本番ビルド時で異なる環境変数を注入するプラグインを作成します。
プラグインの基本構造
typescriptimport type { Plugin, ResolvedConfig } from 'vite';
interface EnvPluginOptions {
prefix?: string;
envFile?: string;
}
export default function envPlugin(
options: EnvPluginOptions = {}
): Plugin {
const { prefix = 'APP_', envFile = '.env' } = options;
let config: ResolvedConfig;
let envVariables: Record<string, string> = {};
return {
name: 'vite-plugin-env-injector',
// ここにフックを実装していきます
};
}
プラグインの基本構造を定義します。オプションで環境変数のプレフィックスと.envファイルのパスを指定できるようにしています。
設定解決フックの実装
typescriptexport default function envPlugin(
options: EnvPluginOptions = {}
): Plugin {
// ... 前述の変数定義
return {
name: 'vite-plugin-env-injector',
// 設定が解決された後に環境変数を読み込む
configResolved(resolvedConfig) {
config = resolvedConfig;
// .env ファイルから環境変数を読み込む
const envPath = path.resolve(config.root, envFile);
if (fs.existsSync(envPath)) {
const envContent = fs.readFileSync(
envPath,
'utf-8'
);
envContent.split('\n').forEach((line) => {
const match = line.match(/^([^=]+)=(.*)$/);
if (match && match[1].startsWith(prefix)) {
envVariables[match[1]] = match[2];
}
});
}
console.log(
`読み込んだ環境変数: ${
Object.keys(envVariables).length
}個`
);
},
};
}
configResolved フックで、最終的な設定を取得し、環境変数ファイルを読み込みます。
コード変換フックの実装
typescriptexport default function envPlugin(
options: EnvPluginOptions = {}
): Plugin {
// ... 前述の実装
return {
name: 'vite-plugin-env-injector',
// ... 前述のフック
// コード内の環境変数プレースホルダーを置換
transform(code, id, transformOptions) {
// node_modules は処理しない
if (id.includes('node_modules')) {
return null;
}
let transformedCode = code;
let hasReplacement = false;
// process.env.APP_* の形式を置換
Object.entries(envVariables).forEach(
([key, value]) => {
const pattern = new RegExp(
`process\\.env\\.${key}`,
'g'
);
if (pattern.test(transformedCode)) {
transformedCode = transformedCode.replace(
pattern,
JSON.stringify(value)
);
hasReplacement = true;
}
}
);
// 変更がなければ null を返す
return hasReplacement
? { code: transformedCode }
: null;
},
};
}
transform フックで、コード内の環境変数参照を実際の値に置き換えます。
開発サーバーフックの実装
typescriptexport default function envPlugin(
options: EnvPluginOptions = {}
): Plugin {
// ... 前述の実装
return {
name: 'vite-plugin-env-injector',
// ... 前述のフック
// 開発サーバーに環境変数情報を表示するエンドポイントを追加
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url === '/__env__') {
res.setHeader('Content-Type', 'application/json');
res.end(
JSON.stringify(
{
variables: Object.keys(envVariables),
count: Object.keys(envVariables).length,
prefix,
},
null,
2
)
);
return;
}
next();
});
console.log(
'環境変数情報: http://localhost:5173/__env__'
);
},
};
}
configureServer フックで、開発サーバーに環境変数の情報を表示するカスタムエンドポイントを追加します。
以下の図は、このプラグインの処理フローを示しています。
mermaidflowchart TD
start["プラグイン初期化"] --> configResolved["configResolved フック"]
configResolved --> readEnv["環境変数ファイル読み込み"]
readEnv --> store["envVariables に保存"]
store --> server["configureServer<br/>(dev 時のみ)"]
server --> middleware["/__env__ エンドポイント追加"]
store --> transform["transform フック"]
transform --> check["process.env.APP_* を検出"]
check -->|"一致あり"| replace["実際の値に置換"]
check -->|"一致なし"| skip["処理スキップ"]
replace --> output["変換後のコード"]
skip --> original["元のコード"]
マークダウンローダープラグイン
マークダウンファイルを Vue コンポーネントに変換するプラグインの例です。
resolveId と load フックの組み合わせ
typescriptimport type { Plugin } from 'vite';
import { marked } from 'marked';
export default function markdownPlugin(): Plugin {
return {
name: 'vite-plugin-markdown',
// .md ファイルを仮想モジュールとして解決
resolveId(id) {
if (id.endsWith('.md')) {
// 実際のファイルパスを返す
return id;
}
return null;
},
};
}
.md ファイルのインポートを許可するために、resolveId でファイルを解決します。
load フックでマークダウンを読み込む
typescriptexport default function markdownPlugin(): Plugin {
return {
name: 'vite-plugin-markdown',
// ... resolveId
// マークダウンファイルの内容を読み込む
load(id) {
if (!id.endsWith('.md')) {
return null;
}
// ファイルを読み込み
const markdown = fs.readFileSync(id, 'utf-8');
// マークダウンを HTML に変換
const html = marked.parse(markdown);
// Vue コンポーネントとしてエクスポート
const component = `
<template>
<div class="markdown-content" v-html="html"></div>
</template>
<script setup>
const html = ${JSON.stringify(html)};
</script>
`;
return {
code: component,
};
},
};
}
load フックで、マークダウンファイルを読み込み、HTML に変換して Vue コンポーネントとして返します。
HMR 対応の追加
typescriptexport default function markdownPlugin(): Plugin {
return {
name: 'vite-plugin-markdown',
// ... 前述のフック
// マークダウンファイルが更新されたときの HMR 処理
handleHotUpdate({ file, server }) {
if (file.endsWith('.md')) {
console.log(
`マークダウンファイルが更新されました: ${file}`
);
// 該当モジュールを再読み込み
const module =
server.moduleGraph.getModuleById(file);
if (module) {
server.moduleGraph.invalidateModule(module);
}
// クライアントに更新を通知
server.ws.send({
type: 'full-reload',
});
return [];
}
},
};
}
handleHotUpdate フックで、マークダウンファイルが更新された際に HMR を適用します。
バンドル分析プラグイン
ビルド後のバンドルサイズを分析して、レポートを生成するプラグインです。
ビルド情報の収集
typescriptimport type { Plugin, OutputBundle } from 'rollup';
interface BundleInfo {
fileName: string;
size: number;
type: 'chunk' | 'asset';
}
export default function bundleAnalyzerPlugin(): Plugin {
const bundleInfos: BundleInfo[] = [];
return {
name: 'vite-plugin-bundle-analyzer',
// バンドル生成時に情報を収集
generateBundle(options, bundle: OutputBundle) {
for (const fileName in bundle) {
const item = bundle[fileName];
let size = 0;
if (item.type === 'chunk') {
size = Buffer.byteLength(item.code, 'utf-8');
} else if (item.type === 'asset') {
size = Buffer.byteLength(item.source, 'utf-8');
}
bundleInfos.push({
fileName,
size,
type: item.type,
});
}
},
};
}
generateBundle フックで、各バンドルファイルのサイズ情報を収集します。
レポート生成
typescriptexport default function bundleAnalyzerPlugin(): Plugin {
// ... 前述の実装
return {
name: 'vite-plugin-bundle-analyzer',
// ... generateBundle フック
// すべてのバンドル処理が完了した後にレポートを生成
closeBundle() {
// サイズでソート
bundleInfos.sort((a, b) => b.size - a.size);
// 合計サイズを計算
const totalSize = bundleInfos.reduce(
(sum, info) => sum + info.size,
0
);
// レポートを生成
console.log('\n📊 バンドル分析レポート\n');
console.log(`合計サイズ: ${formatSize(totalSize)}\n`);
bundleInfos.forEach((info, index) => {
const percentage = (
(info.size / totalSize) *
100
).toFixed(2);
console.log(
`${index + 1}. ${info.fileName} (${
info.type
}): ${formatSize(info.size)} (${percentage}%)`
);
});
// JSON ファイルとして出力
const reportPath = 'dist/bundle-report.json';
fs.writeFileSync(
reportPath,
JSON.stringify(
{ total: totalSize, bundles: bundleInfos },
null,
2
)
);
console.log(`\n✅ 詳細レポート: ${reportPath}\n`);
},
};
}
// サイズをフォーマットする補助関数
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024)
return `${(bytes / 1024).toFixed(2)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
}
closeBundle フックで、収集した情報を基にレポートを生成します。コンソールに出力するだけでなく、JSON ファイルとしても保存します。
以下の図は、バンドル分析プラグインの処理フローを示しています。
mermaidflowchart TD
build["ビルド開始"] --> generate["generateBundle フック"]
generate --> loop["各バンドルファイルを<br/>ループ処理"]
loop --> chunk{type は?}
chunk -->|chunk| codeSize["code のサイズ計算"]
chunk -->|asset| assetSize["source のサイズ計算"]
codeSize --> collect["bundleInfos に追加"]
assetSize --> collect
collect --> next{次のファイル?}
next -->|"あり"| loop
next -->|"なし"| close["closeBundle フック"]
close --> sort["サイズでソート"]
sort --> calc["合計サイズ計算"]
calc --> console["コンソール出力"]
console --> json["JSON ファイル出力"]
json --> done["完了"]
フック実行順序の確認
実際にプラグインを作成して、各フックがどの順序で実行されるかを確認してみます。
実行順序確認プラグイン
typescriptimport type { Plugin } from 'vite';
export default function hookOrderPlugin(): Plugin {
let counter = 0;
const log = (hookName: string, detail?: string) => {
console.log(
`[${++counter}] ${hookName}${
detail ? ': ' + detail : ''
}`
);
};
return {
name: 'hook-order-debugger',
// Vite 専用フック(設定フェーズ)
config() {
log('config');
},
configResolved() {
log('configResolved');
},
configureServer() {
log('configureServer');
},
};
}
設定フェーズのフックをログ出力します。
ビルドフェーズのフック
typescriptexport default function hookOrderPlugin(): Plugin {
// ... 前述の実装
return {
name: 'hook-order-debugger',
// ... 設定フェーズのフック
// ビルドフェーズ
options(opts) {
log('options');
return opts;
},
buildStart() {
log('buildStart');
},
resolveId(source, importer) {
log('resolveId', source);
return null;
},
load(id) {
log('load', id);
return null;
},
transform(code, id) {
log('transform', id);
return null;
},
};
}
モジュール解決とコード変換のフックをログ出力します。これらは各モジュールごとに複数回実行されます。
出力フェーズのフック
typescriptexport default function hookOrderPlugin(): Plugin {
// ... 前述の実装
return {
name: 'hook-order-debugger',
// ... 前述のフック
buildEnd() {
log('buildEnd');
},
// 出力生成フェーズ
outputOptions(opts) {
log('outputOptions');
return opts;
},
renderStart() {
log('renderStart');
},
renderChunk(code, chunk) {
log('renderChunk', chunk.fileName);
return null;
},
generateBundle() {
log('generateBundle');
},
writeBundle() {
log('writeBundle');
},
closeBundle() {
log('closeBundle');
},
};
}
出力生成からファイル書き込みまでのフックをログ出力します。
このプラグインを実行すると、以下のような順序でフックが実行されることが確認できます。
開発サーバー起動時:
csharp[1] config
[2] configResolved
[3] configureServer
[4] buildStart
本番ビルド時:
less[1] config
[2] configResolved
[3] options
[4] buildStart
[5] resolveId: ./src/main.ts
[6] load: /path/to/src/main.ts
[7] transform: /path/to/src/main.ts
... (各モジュールごとに resolveId → load → transform が繰り返される)
[N] buildEnd
[N+1] outputOptions
[N+2] renderStart
[N+3] renderChunk: index.js
[N+4] generateBundle
[N+5] writeBundle
[N+6] closeBundle
まとめ
Vite のプラグインシステムは、Rollup のフックを基盤としつつ、開発サーバーや HMR といった Vite 特有の機能に対応するための独自フックを追加しています。
重要なポイント:
- Rollup フックの継承:
resolveId、load、transform、generateBundleなどの基本フックは Rollup と互換性があります - Vite による拡張: SSR パラメータや追加のコンテキスト情報が提供され、より柔軟な処理が可能です
- Vite 専用フック:
config、configureServer、transformIndexHtml、handleHotUpdateなどは Vite でのみ使用できます - 実行順序の理解: 設定 → ビルド → 出力の順序で各フックが実行されることを把握することが重要です
- 適切なフック選択: 目的に応じて最適なフックを選択することで、効率的なプラグイン開発が可能です
Rollup プラグインを Vite に移行する際は、基本的な互換性を活かしつつ、Vite 専用フックを追加することで、より優れた開発体験を提供できます。
本記事で紹介したフック対応表と具体例を参考に、効果的な Vite プラグイン開発を進めていただければ幸いです。
関連リンク
articleVite プラグインフック対応表:Rollup → Vite マッピング早見表
articleVite で Web Worker / SharedWorker を TypeScript でバンドルする初期設定
articleテスト環境比較:Vitest vs Jest vs Playwright CT ― Vite プロジェクトの最適解
articleVite CSS HMR が反映されない時のチェックリスト:PostCSS/Modules/Cache 編
articleesbuild プリバンドルを理解する:Vite の optimizeDeps 深掘り
articleVite 本番の可観測性:ソースマップアップロードと Sentry 連携でエラーを特定
articleVite プラグインフック対応表:Rollup → Vite マッピング早見表
articleNestJS Monorepo 構築:Nx/Yarn Workspaces で API・Lib を一元管理
articleTypeScript Project References 入門:大規模 Monorepo で高速ビルドを実現する設定手順
articleMySQL Router セットアップ完全版:アプリからの透過フェイルオーバーを実現
articletRPC アーキテクチャ設計:BFF とドメイン分割で肥大化を防ぐルータ戦略
articleMotion(旧 Framer Motion)× TypeScript:Variant 型と Props 推論を強化する設定レシピ
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来