Viteプラグイン開発の真髄:ビルドパイプラインを支配し、開発体験を極限まで高める
こんにちは。日々のビルド待ち時間にコーヒーを淹れすぎるエンジニアの皆さん、あるいは「なぜかプラグインが動かない」と深夜に頭を抱えた経験のある皆さん。
Viteは確かに「爆速」ですが、その裏側で何が起きているのかを理解せずに使うのは、ブラックボックスのスポーツカーを運転するようなものです。今回は、Vite(その心臓部であるRollup)のプラグイン開発における「フックの実行順序」という、いわばビルドの心臓部にメスを入れます。
これをマスターすれば、単なるライブラリ利用者から、プロジェクトのビルドプロセスを自在に操る「アーキテクト」へと進化できます。
—
1. なぜ「フックの順序」を知る必要があるのか?
Viteのビルドは、「ソースコードを読み込み、変換し、統合し、ファイルとして吐き出す」という一連のパイプラインです。
もし、あなたが「ビルドの直前にファイルを自動生成したい」と思ったとき、どのフックにコードを書くべきでしょうか?
- `buildStart`?
- `transform`?
- `generateBundle`?
ここを間違えると、「ファイルが生成される前にビルドが完了してしまう」「後続のプラグインが読み込むはずのデータがまだ存在しない」といった、デバッグ地獄に直面します。パイプラインの川の流れを知ることは、開発の安定性を担保するための必須教養なのです。
—
2. ビルドパイプラインの全体図:川の流れを理解する
Vite/Rollupのフックは、大まかに以下の順序で流れます。
1. `buildStart`: ビルドプロセスの開始。初期設定やログの出力に最適。
2. `resolveId`: モジュールのパスを解決(import文から実際のファイルパスへ)。
3. `load`: ファイルの中身をメモリに読み込む。
4. `transform`: コードの変換(TypeScriptのコンパイルやJSX変換など)。
5. `generateBundle`: 最終的なチャンク構成が決まった直後。書き出し前の「最後の悪あがき」が可能。
6. `writeBundle`: 実際にファイルがディスクに書き込まれた後。
7. `closeBundle`: 全てのプロセスが終了。後処理に最適。
—
3. 実践:HelloWorldプラグインでフックの動きを覗く
まずは、どのタイミングで何が起きているのかを可視化する「観測用プラグイン」を作ってみましょう。
`my-plugin.js` を作成します。
// 現場で震えるほど役立つ、フックの実行順序を可視化するプラグイン
export default function myObserverPlugin() {
return {
name: ‘vite-plugin-observer’,
buildStart() {
console.log(‘🚀 [1] buildStart: ビルドが始まりました。環境変数の検証などに最適です。’);
},
transform(code, id) {
// 頻繁に呼ばれるため、本番では慎重にフィルタリングすること
if (id.endsWith(‘.js’)) {
// console.log(`🔍 [2] transform: ${id} を変換中…`);
}
},
generateBundle(options, bundle) {
console.log(‘📦 [3] generateBundle: 出力直前。ここでファイルを追加することも可能です。’);
},
closeBundle() {
console.log(‘✅ [4] closeBundle: 全ての書き出しが完了しました。’);
}
};
}
これを `vite.config.js` に読み込ませてみてください。
import { defineConfig } from ‘vite’;
import myObserverPlugin from ‘./my-plugin’;
export default defineConfig({
plugins: [myObserverPlugin()]
});
ビルドコマンド(`npm run build`)を打つと、ログの順序からビルドの構造が手に取るようにわかるはずです。
—
4. 競合を避ける:`enforce` の魔法
複数のプラグインを導入すると、フックの実行順序がバラバラになり、「Aが処理した後にBが処理してほしいのに!」という状況が多発します。ここで登場するのが `enforce` です。
- `pre`: 他のプラグインよりも先に実行。
- `post`: 他のプラグインよりも後に実行。
例えば、ビルドの最後に独自のメタデータをJSONとして出力したい場合、`enforce: ‘post’` を指定することで、他のあらゆる変換が終わった後に確実に実行されることが保証されます。
export default function myFinalizerPlugin() {
return {
name: ‘my-finalizer’,
enforce: ‘post’, // 他のプラグインの処理が終わるのを待つ
generateBundle() {
// ここで最終的な成果物をフックして、外部へ送信したりレポートを作る
}
};
}
—
5. アーキテクトからのアドバイス:なぜこれを知ると楽になるのか
多くの開発者は「プラグインが動かない」とき、闇雲にコードを書き直します。しかし、ビルドパイプラインを理解していると、「どのフェーズでデータが欠落しているか」が論理的に推測できるようになります。
- `transform` でやるべきこと:コードの中身を書き換える(Babel, TS, PostCSS)。
- `generateBundle` でやるべきこと:生成物全体を操作する(ファイル分割、マニフェスト生成)。
- `closeBundle` でやるべきこと:外部システムとの連携(Slack通知、デプロイ後のクリーンアップ)。
この切り分けができるだけで、あなたの書くプラグインは驚くほど堅牢になり、チーム全員の生産性を底上げする「開発の資産」へと昇華されます。
—
まとめ:次に進むために
まずは、自分のプロジェクトにある既存のプラグインを一つ選んで、上記のように `console.log` を仕込んでみてください。「あ、ここで変換してるんだ」「ここはビルドの最後だったのか」という気付きが、あなたを一段上のエンジニアへと引き上げます。
Viteのビルドパイプラインは、魔法ではありません。緻密に設計された「コードの工場の流れ」です。この工場のラインを理解したとき、あなたはもう「ただの利用者」ではなく、ツールを使いこなす「エンジニア」になっているはずです。
何か分からないことがあれば、いつでもまた聞きに来てください。一緒に最高の環境を創り上げていきましょう。