プラグイン 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()],
})
偽のプラグインは無視されます。これは、プラグインの有効化または無効化を簡単に実行するために使用できます。
`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ドキュメントで詳細を読むことができます。
一部の`resolveId`呼び出しの`importer`値は、ルートの汎用`index.html`の絶対パスになる場合があります。これは、Viteのバンドルされていない開発サーバーパターンにより、実際のインポーターを導き出すことが常に可能とは限らないためです。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`、`parallel`
Viteの設定が解決された後に呼び出されます。このフックを使用して、最終的に解決された設定を読み込んで保存します。実行されているコマンドに基づいて異なる処理を行う必要がある場合にも役立ちます。
例
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... }) } }, })
サーバーアクセスの保存
場合によっては、他のプラグインフックが開発サーバーインスタンスへのアクセスを必要とする場合があります(例:Webソケットサーバー、ファイルシステムウォッチャー、またはモジュールグラフへのアクセス)。このフックは、他のフックでアクセスするためにサーバーインスタンスを保存するためにも使用できます。
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`、`sequential`
index.html
などのHTMLエントリポイントファイルを変換するための専用のフックです。このフックは、現在のHTML文字列と変換コンテキストを受け取ります。コンテキストは、開発中はViteDevServer
インスタンスを公開し、ビルド中はRollup出力バンドルを公開します。このフックは非同期にすることができ、以下のいずれかを返すことができます。
- 変換されたHTML文字列
- 既存のHTMLに挿入するタグ記述子オブジェクトの配列(
{ tag, attrs, children }
)。各タグは、どこに挿入するべきかを指定することもできます(デフォルトは<head>
の先頭です)。 { html, tags }
として両方を含むオブジェクト
デフォルトでは
order
はundefined
で、HTMLの変換後にこのフックが適用されます。Viteプラグインパイプラインを通過する必要があるスクリプトを挿入するには、order: 'pre'
を使用すると、HTMLを処理する前にフックが適用されます。order: 'post'
は、order
がundefinedであるすべてのフックが適用された後にフックを適用します。基本例
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ポストビルドプラグイン(圧縮、マニフェスト、レポート)
これはフックの順序とは別であることに注意してください。それらは依然として、Rollupフックの場合と同様に、個別にorder
属性の影響を受けます。
条件付き適用
デフォルトでは、プラグインはサービスとビルドの両方で呼び出されます。プラグインをサービスまたはビルドの間にのみ条件付きで適用する必要がある場合は、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は、IDの解決中にパスを正規化してPOSIXセパレーター(/)を使用しますが、Windowsではボリュームを保持します。一方、Rollupはデフォルトでは解決済みのパスをそのままにしておくため、Windowsでは解決済みのIDに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固有のプラグインと統合で標準のinclude / excludeフィルタリングパターンを使用することを推奨しています。これはViteコア自体でも使用されています。
クライアントサーバー間の通信
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
}
}
このインターフェース拡張は、InferCustomEventPayload<T>
によって、イベント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
})