プラグイン API
Vite のプラグインは、Rollup の優れたプラグインインターフェースにいくつかの Vite 固有のオプションを追加して拡張します。その結果、Vite プラグインを一度書けば、開発とビルドの両方で動作させることができます。
以下のセクションを読む前に、まずRollup のプラグインドキュメントを読むことをお勧めします。
プラグインの作成
Vite はすぐに使える確立されたパターンを提供するように努めているため、新しいプラグインを作成する前に、機能ガイドを確認して、必要なものがカバーされていることを確認してください。また、利用可能なコミュニティプラグインとして、互換性のある Rollup プラグインとVite 固有のプラグインの両方を確認してください。
プラグインを作成するときは、vite.config.js にインラインで記述できます。新しいパッケージを作成する必要はありません。プラグインがプロジェクトで役立つことがわかったら、エコシステムの他の人を助けるために共有することを検討してください。
ヒント
学習、デバッグ、またはプラグインの作成中は、プロジェクトにvite-plugin-inspectを含めることをお勧めします。これにより、Vite プラグインの中間状態を検査できます。インストール後、localhost:5173/__inspect/ にアクセスして、プロジェクトのモジュールと変換スタックを検査できます。インストール手順については、vite-plugin-inspect ドキュメントを確認してください。 
規則
プラグインが Vite 固有のフックを使用せず、互換性のある Rollup プラグインとして実装できる場合は、Rollup プラグインの命名規則を使用することをお勧めします。
- Rollup プラグインは、
rollup-plugin-のプレフィックスを持つ明確な名前を持つ必要があります。 - package.json に
rollup-pluginとvite-pluginのキーワードを含めます。
これにより、プラグインは純粋な Rollup または WMR ベースのプロジェクトでも使用できるようになります。
Vite 専用プラグインの場合
- Vite プラグインは、
vite-plugin-のプレフィックスを持つ明確な名前を持つ必要があります。 - package.json に
vite-pluginのキーワードを含めます。 - プラグインのドキュメントに、なぜ Vite 専用プラグインであるか(例えば、Vite 固有のプラグインフックを使用しているためなど)を詳しく説明するセクションを含めます。
プラグインが特定のフレームワークでのみ機能する場合、その名前をプレフィックスの一部に含める必要があります。
- Vue プラグインには
vite-plugin-vue-のプレフィックス - React プラグインには
vite-plugin-react-のプレフィックス - Svelte プラグインには
vite-plugin-svelte-のプレフィックス
仮想モジュールの規則も参照してください。
プラグイン設定
ユーザーは、プロジェクトの devDependencies にプラグインを追加し、plugins 配列オプションを使用して設定します。
import vitePlugin from 'vite-plugin-feature'
import rollupPlugin from 'rollup-plugin-feature'
export default defineConfig({
plugins: [vitePlugin(), rollupPlugin()],
})Falsy なプラグインは無視されます。これは、プラグインを簡単に有効または無効にするために使用できます。
plugins は、単一の要素としていくつかのプラグインを含むプリセットも受け入れます。これは、いくつかのプラグインを使用して実装される複雑な機能(フレームワーク統合など)に役立ちます。配列は内部でフラット化されます。
// framework-plugin
import frameworkRefresh from 'vite-plugin-framework-refresh'
import frameworkDevtools from 'vite-plugin-framework-devtools'
export default function framework(config) {
return [frameworkRefresh(config), frameworkDevTools(config)]
}import { defineConfig } from 'vite'
import framework from 'vite-plugin-framework'
export default defineConfig({
plugins: [framework()],
})簡単な例
ヒント
Vite/Rollup プラグインを、実際のプラグインオブジェクトを返すファクトリ関数として作成するのが一般的な慣習です。この関数は、ユーザーがプラグインの動作をカスタマイズできるオプションを受け入れることができます。
カスタムファイルタイプの変換
const fileRegex = /\.(my-file-ext)$/
export default function myPlugin() {
return {
name: 'transform-file',
transform(src, id) {
if (fileRegex.test(id)) {
return {
code: compileFileToJS(src),
map: null, // provide source map if available
}
}
},
}
}仮想ファイルのインポート
次のセクションの例を参照してください。
仮想モジュールの規則
仮想モジュールは、通常の ESM インポート構文を使用してビルド時の情報をソースファイルに渡すことができる便利なスキームです。
export default function myPlugin() {
const virtualModuleId = 'virtual:my-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'my-plugin', // required, will show up in warnings and errors
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `export const msg = "from virtual module"`
}
},
}
}これにより、JavaScript でモジュールをインポートできます。
import { msg } from 'virtual:my-module'
console.log(msg)Vite (および Rollup) の仮想モジュールは、ユーザー向けのパスには慣習として virtual: がプレフィックスされます。可能であれば、エコシステム内の他のプラグインとの衝突を避けるために、プラグイン名を名前空間として使用する必要があります。たとえば、vite-plugin-posts は、ユーザーに virtual:posts または virtual:posts/helpers の仮想モジュールをインポートしてビルド時の情報を取得するように求めることができます。内部的には、仮想モジュールを使用するプラグインは、id を解決する際にモジュール ID に \0 をプレフィックスする必要があります。これは Rollup エコシステムからの慣習です。これにより、他のプラグインが id を処理しようとするのを防ぎ (ノード解決など)、ソースマップなどのコア機能は、この情報を使用して仮想モジュールと通常のファイルを区別できます。\0 はインポート URL で許可されない文字であるため、インポート解析中にそれらを置き換える必要があります。\0{id} 仮想 id は、ブラウザの開発中に /@id/__x00__{id} としてエンコードされます。id はプラグインパイプラインに入る前にデコードされるため、プラグインフックコードからは見えません。
ただし、単一ファイルコンポーネント (例: .vue または .svelte SFC) のスクリプトモジュールのケースのように、実際のファイルから直接派生したモジュールは、この規則に従う必要はありません。SFC は通常、処理時に一連のサブモジュールを生成しますが、これらのコードはファイルシステムにマッピングし直すことができます。これらのサブモジュールに \0 を使用すると、ソースマップが正しく機能しなくなります。
ユニバーサルなフック
開発中、Vite 開発サーバーは、Rollup と同じ方法でRollup ビルドフックを呼び出すプラグインコンテナを作成します。
以下のフックは、サーバー起動時に一度呼び出されます。
以下のフックは、モジュールのリクエストごとに呼び出されます。
これらのフックには、Vite 固有の追加プロパティを持つ拡張された options パラメーターもあります。SSR ドキュメントで詳細を読むことができます。
Vite のバンドルされない開発サーバーのパターンにより、実際のインポーターを常に導出できるわけではないため、一部の resolveId 呼び出しの importer 値は、ルートにある一般的な index.html の絶対パスになる場合があります。Vite の解決パイプライン内で処理されるインポートの場合、インポート解析フェーズ中にインポーターを追跡できるため、正しい importer 値が提供されます。
以下のフックは、サーバーが閉じられたときに呼び出されます。
開発中には、moduleParsed フックは呼び出されません。これは、Vite がパフォーマンス向上のために完全な AST パースを避けているためです。
出力生成フック (closeBundle を除く) は、開発中には呼び出されません。Vite の開発サーバーは rollup.rollup() を呼び出すだけで、bundle.generate() は呼び出さないと考えることができます。
Vite 固有のフック
Vite プラグインは、Vite 固有の目的を果たすフックも提供できます。これらのフックは Rollup によって無視されます。
config
型:
(config: UserConfig, env: { mode: string, command: string }) => UserConfig | null | void種類:
async,sequential解決される前に Vite の設定を変更します。このフックは、生のユーザー設定(CLI オプションと設定ファイルの結合)と、使用されている
modeおよびcommandを公開する現在の設定環境を受け取ります。既存の設定に深くマージされる部分的な設定オブジェクトを返すことも、設定を直接変更することもできます(デフォルトのマージで目的の結果が得られない場合)。例
js// return partial config (recommended) const partialConfigPlugin = () => ({ name: 'return-partial', config: () => ({ resolve: { alias: { foo: 'bar', }, }, }), }) // mutate the config directly (use only when merging doesn't work) const mutateConfigPlugin = () => ({ name: 'mutate-config', config(config, { command }) { if (command === 'build') { config.root = 'foo' } }, })注意
ユーザープラグインは、このフックが実行される前に解決されるため、
configフック内に他のプラグインを注入しても効果はありません。
configResolved
型:
(config: ResolvedConfig) => void | Promise<void>種類:
async,parallelVite の設定が解決された後に呼び出されます。このフックを使用して、最終的に解決された設定を読み込み、保存します。実行中のコマンドに基づいてプラグインが異なる動作をする必要がある場合にも便利です。
例
jsconst examplePlugin = () => { let config return { name: 'read-config', configResolved(resolvedConfig) { // store the resolved config config = resolvedConfig }, // use stored config in other hooks transform(code, id) { if (config.command === 'serve') { // dev: plugin invoked by dev server } else { // build: plugin invoked by Rollup } }, } }開発中の
commandの値はserveであることに注意してください(CLI ではvite、vite dev、vite serveはエイリアスです)。
configureServer
型:
(server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>種類:
async,sequential参照: ViteDevServer
開発サーバーを設定するためのフックです。最も一般的なユースケースは、内部の connect アプリにカスタムミドルウェアを追加することです。
jsconst myPlugin = () => ({ name: 'configure-server', configureServer(server) { server.middlewares.use((req, res, next) => { // custom handle request... }) }, })ポストミドルウェアの注入
configureServerフックは内部ミドルウェアがインストールされる前に呼び出されるため、カスタムミドルウェアはデフォルトで内部ミドルウェアの前に実行されます。内部ミドルウェアの**後に**ミドルウェアを注入したい場合は、configureServerから関数を返すことができます。これは内部ミドルウェアがインストールされた後に呼び出されます。jsconst myPlugin = () => ({ name: 'configure-server', configureServer(server) { // return a post hook that is called after internal middlewares are // installed return () => { server.middlewares.use((req, res, next) => { // custom handle request... }) } }, })サーバーアクセスの保存
場合によっては、他のプラグインフックが開発サーバーインスタンスへのアクセスを必要とする場合があります(例: WebSocket サーバー、ファイルシステムウォッチャー、モジュールグラフへのアクセス)。このフックを使用して、他のフックからアクセスできるようにサーバーインスタンスを保存することもできます。
jsconst myPlugin = () => { let server return { name: 'configure-server', configureServer(_server) { server = _server }, transform(code, id) { if (server) { // use server... } }, } }configureServerは本番ビルドの実行時には呼び出されないため、他のフックではその不在を保護する必要があります。
configurePreviewServer
型:
(server: PreviewServer) => (() => void) | void | Promise<(() => void) | void>種類:
async,sequential参照: PreviewServer
configureServerと同じですが、プレビューサーバー用です。configureServerと同様に、configurePreviewServerフックは他のミドルウェアがインストールされる前に呼び出されます。他のミドルウェアの**後に**ミドルウェアを注入したい場合は、configurePreviewServerから関数を返すことができます。これは内部ミドルウェアがインストールされた後に呼び出されます。jsconst myPlugin = () => ({ name: 'configure-preview-server', configurePreviewServer(server) { // return a post hook that is called after other middlewares are // installed return () => { server.middlewares.use((req, res, next) => { // custom handle request... }) } }, })
transformIndexHtml
型:
IndexHtmlTransformHook | { order?: 'pre' | 'post', handler: IndexHtmlTransformHook }種類:
async,sequentialindex.htmlのような HTML エントリーポイントファイルを変換するための専用フックです。このフックは、現在の HTML 文字列と変換コンテキストを受け取ります。開発中は、コンテキストはViteDevServerインスタンスを公開し、ビルド中は Rollup の出力バンドルを公開します。このフックは非同期で、以下のいずれかを返すことができます。
- 変換された HTML 文字列
- 既存の HTML に挿入するタグ記述子オブジェクト (
{ tag, attrs, children }) の配列。各タグは、どこに挿入するかを指定することもできます (デフォルトは<head>の先頭に追加)。 { html, tags }の両方を含むオブジェクト
デフォルトでは
orderはundefinedで、このフックは HTML が変換された後に適用されます。Vite プラグインパイプラインを通過する必要があるスクリプトを注入するには、order: 'pre'を指定すると、HTML が処理される前にフックが適用されます。order: 'post'は、orderが未定義のすべてのフックが適用された後にフックを適用します。基本的な例
jsconst htmlPlugin = () => { return { name: 'html-transform', transformIndexHtml(html) { return html.replace( /<title>(.*?)<\/title>/, `<title>Title replaced!</title>`, ) }, } }完全なフックシグネチャ
tstype IndexHtmlTransformHook = ( html: string, ctx: { path: string filename: string server?: ViteDevServer bundle?: import('rollup').OutputBundle chunk?: import('rollup').OutputChunk }, ) => | IndexHtmlTransformResult | void | Promise<IndexHtmlTransformResult | void> type IndexHtmlTransformResult = | string | HtmlTagDescriptor[] | { html: string tags: HtmlTagDescriptor[] } interface HtmlTagDescriptor { tag: string attrs?: Record<string, string | boolean> children?: string | HtmlTagDescriptor[] /** * default: 'head-prepend' */ injectTo?: 'head' | 'body' | 'head-prepend' | 'body-prepend' }注意
エントリーファイルのカスタム処理を行うフレームワーク(たとえば、SvelteKit)を使用している場合、このフックは呼び出されません。
handleHotUpdate
型:
(ctx: HmrContext) => Array<ModuleNode> | void | Promise<Array<ModuleNode> | void>参照: HMR API
カスタム HMR 更新処理を実行します。このフックは、以下のシグネチャを持つコンテキストオブジェクトを受け取ります。
tsinterface HmrContext { file: string timestamp: number modules: Array<ModuleNode> read: () => string | Promise<string> server: ViteDevServer }modulesは、変更されたファイルの影響を受けるモジュールの配列です。単一のファイルが複数の提供されるモジュール(例: Vue SFC)にマップされる可能性があるため、配列になります。readは、ファイルの内容を返す非同期読み取り関数です。これは、一部のシステムでは、エディタがファイルを更新する前にファイル変更コールバックが速く発火し、直接のfs.readFileが空の内容を返す可能性があるため提供されます。渡される読み取り関数は、この動作を正規化します。
フックは以下を選択できます。
影響を受けるモジュールリストをフィルタリングして絞り込み、HMRをより正確にします。
空の配列を返し、完全なリロードを実行します。
jshandleHotUpdate({ server, modules, timestamp }) { // Invalidate modules manually const invalidatedModules = new Set() for (const mod of modules) { server.moduleGraph.invalidateModule( mod, invalidatedModules, timestamp, true ) } server.ws.send({ type: 'full-reload' }) return [] }空の配列を返し、カスタムイベントをクライアントに送信することで、完全にカスタムのHMR処理を実行します。
jshandleHotUpdate({ server }) { server.ws.send({ type: 'custom', event: 'special-update', data: {} }) return [] }クライアントコードは、HMR API を使用して対応するハンドラーを登録する必要があります(これは、同じプラグインの
transformフックによって注入される可能性があります)。jsif (import.meta.hot) { import.meta.hot.on('special-update', (data) => { // perform custom update }) }
プラグインの順序
Vite プラグインは、その適用順序を調整するために、追加で enforce プロパティ (webpack ローダーと同様) を指定できます。enforce の値は "pre" または "post" のいずれかになります。解決されたプラグインは以下の順序になります。
- エイリアス
enforce: 'pre'を持つユーザープラグイン- Vite コアプラグイン
- enforce 値を持たないユーザープラグイン
- Vite ビルドプラグイン
enforce: 'post'を持つユーザープラグイン- Vite ビルド後プラグイン (minify, manifest, reporting)
これはフックの順序とは別であることに注意してください。フックは、Rollup フックの usual for Rollup hooks と同様に、それぞれの order 属性に従って個別に順序付けられます。
条件付き適用
デフォルトでは、プラグインは serve と build の両方で呼び出されます。プラグインを serve または build のどちらかでのみ条件付きで適用する必要がある場合は、apply プロパティを使用して 'build' または 'serve' の間のみ呼び出すようにします。
function myPlugin() {
return {
name: 'build-only',
apply: 'build', // or 'serve'
}
}より厳密な制御のために関数を使用することもできます。
apply(config, { command }) {
// apply only on build but not for SSR
return command === 'build' && !config.build.ssr
}Rollup プラグインの互換性
多くの Rollup プラグインは Vite プラグインとして直接動作します(例:@rollup/plugin-alias や @rollup/plugin-json)が、すべてではありません。これは、一部のプラグインフックがバンドルされていない開発サーバーのコンテキストでは意味をなさないためです。
一般的に、Rollup プラグインが以下の基準を満たしていれば、Vite プラグインとして機能するはずです。
moduleParsedフックを使用しないこと。- バンドルフェーズフックと出力フェーズフックの間に強い結合がないこと。
Rollup プラグインがビルドフェーズのみに意味がある場合、代わりに build.rollupOptions.plugins の下に指定できます。これは、enforce: 'post' および apply: 'build' を持つ Vite プラグインと同じように機能します。
既存の Rollup プラグインを Vite 固有のプロパティで拡張することもできます。
import example from 'rollup-plugin-example'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
{
...example(),
enforce: 'post',
apply: 'build',
},
],
})パスの正規化
Vite は、Windows でボリュームを維持しながら、id を解決するときにパスを正規化して POSIX セパレータ (/) を使用します。一方、Rollup はデフォルトで解決されたパスをそのまま保持するため、解決された id には Windows で win32 セパレータ (\) が含まれます。ただし、Rollup プラグインは内部的に @rollup/pluginutils の normalizePath ユーティリティ関数 を使用しており、比較を実行する前にセパレータを POSIX に変換します。これは、これらのプラグインが Vite で使用される場合、include および exclude 設定パターンと解決された id の比較に対する他の同様のパスが正しく機能することを意味します。
したがって、Vite プラグインの場合、パスと解決された id を比較するときは、まずパスを正規化して POSIX セパレータを使用することが重要です。同等の normalizePath ユーティリティ関数は vite モジュールからエクスポートされています。
import { normalizePath } from 'vite'
normalizePath('foo\\bar') // 'foo/bar'
normalizePath('foo/bar') // 'foo/bar'フィルタリング、include/exclude パターン
Vite は @rollup/pluginutils の createFilter 関数を公開しており、Vite 固有のプラグインや統合で、Vite コア自体でも使用されている標準の include/exclude フィルタリングパターンを使用することを推奨しています。
クライアントとサーバー間の通信
Vite 2.9 以降、クライアントとの通信を処理するのに役立つユーティリティをプラグインに提供しています。
サーバーからクライアントへ
プラグイン側では、server.ws.send を使用してイベントをクライアントにブロードキャストできます。
export default defineConfig({
plugins: [
{
// ...
configureServer(server) {
server.ws.on('connection', () => {
server.ws.send('my:greetings', { msg: 'hello' })
})
},
},
],
})注意
他のプラグインとの衝突を避けるため、イベント名には常にプレフィックスを付けることをお勧めします。
クライアント側では、hot.on を使用してイベントをリッスンします。
// client side
if (import.meta.hot) {
import.meta.hot.on('my:greetings', (data) => {
console.log(data.msg) // hello
})
}クライアントからサーバーへ
クライアントからサーバーへイベントを送信するには、hot.send を使用できます。
// client side
if (import.meta.hot) {
import.meta.hot.send('my:from-client', { msg: 'Hey!' })
}次に、server.ws.on を使用してサーバー側でイベントをリッスンします。
export default defineConfig({
plugins: [
{
// ...
configureServer(server) {
server.ws.on('my:from-client', (data, client) => {
console.log('Message from client:', data.msg) // Hey!
// reply only to the client (if needed)
client.send('my:ack', { msg: 'Hi! I got your message!' })
})
},
},
],
})カスタムイベントの TypeScript
内部的に、vite は CustomEventMap インターフェースからペイロードの型を推論します。インターフェースを拡張することでカスタムイベントに型を付けることができます。
注意
TypeScript 宣言ファイルを指定する際は、必ず .d.ts 拡張子を含めてください。そうしないと、TypeScript がモジュールがどのファイルを拡張しようとしているのかを認識できない場合があります。
import 'vite/types/customEvent.d.ts'
declare module 'vite/types/customEvent.d.ts' {
interface CustomEventMap {
'custom:foo': { msg: string }
// 'event-key': payload
}
}このインターフェース拡張は、イベント T のペイロード型を推論するために InferCustomEventPayload<T> によって利用されます。このインターフェースがどのように利用されるかの詳細については、HMR API ドキュメントを参照してください。
type CustomFooPayload = InferCustomEventPayload<'custom:foo'>
import.meta.hot?.on('custom:foo', (payload) => {
// The type of payload will be { msg: string }
})
import.meta.hot?.on('unknown:event', (payload) => {
// The type of payload will be any
})