Back

Viteでプラグインをビルドして使用する方法

Viteでプラグインをビルドして使用する方法

カスタムViteプラグインを作成することで、デフォルト設定を超えてビルドプロセスを拡張できます。カスタムファイルタイプの変換、ビルド時のロジック注入、開発サーバーミドルウェアの追加など、様々なニーズに対応できます。標準的なVite設定の限界に達した場合、独自のプラグインを構築することが自然な次のステップとなります。

このガイドでは、ViteのPlugin APIの仕組み、使用する主要なライフサイクルフック、そして実践的な例を通じて、基本的な概念をカバーします。開発ワークフローにおける実際の問題を解決するプラグインの作成、テスト、公開方法を学びます。

重要なポイント

  • Viteプラグインは、ライフサイクルフックを通じて開発環境と本番ビルドプロセスの両方を拡張します
  • ほとんどのRollupプラグインはViteで動作しますが、開発サーバー機能にはVite固有のフックが必要です
  • 仮想モジュールなどの高度なパターンに進む前に、シンプルな変換から始めましょう
  • プラグインを公開する際は、命名規則とドキュメント標準に従いましょう

ViteプラグインとRollupとの関係を理解する

Viteプラグインは、開発とビルドプロセスの異なる段階にフックするJavaScriptオブジェクトです。Rollupのプラグインシステムをベースに構築されており、Viteの開発サーバー固有の追加フックを備えています。

Viteは内部で2つの異なるビルドツールを使用しています。esbuildがネイティブESモジュールを使用した高速な開発サーバーを駆動し、Rollupが最適な出力のための本番バンドルを処理します。このデュアルアーキテクチャにより、プラグインはapplyオプションを使用して特定の環境をターゲットにできます。

function myPlugin(): Plugin {
  return {
    name: 'my-plugin',
    apply: 'serve', // 開発時のみ実行
    // ... フック
  }
}

ほとんどのRollupプラグインは、変更なしでViteで動作します。ただし、ミドルウェアの追加や開発中のHTML変更など、開発サーバー機能が必要な場合は、Vite固有のフックが必要になります。

基本的なプラグイン構造と設定

すべてのViteプラグインには、nameプロパティと少なくとも1つのフック関数が必要です。

import type { Plugin } from 'vite'

function myPlugin(options?: MyPluginOptions): Plugin {
  return {
    name: 'vite-plugin-example',
    enforce: 'pre', // オプション: プラグインの実行順序を制御
    apply: 'build', // オプション: 'serve' | 'build' | undefined
    
    // フック関数をここに記述
    transform(code, id) {
      if (id.endsWith('.custom')) {
        return transformCustomFile(code)
      }
    }
  }
}

Plugin APIは、すべてのフックとオプションのTypeScript定義を提供します。完全な型安全性と自動補完のために、‘vite’からPlugin型をインポートしてください。

Plugin APIの重要なライフサイクルフック

ビルドプロセスの異なる段階で、異なるフックが発火します。最もよく使用されるものは以下の通りです。

設定フック

  • config: 解決される前にVite設定を変更
  • configResolved: 最終的に解決された設定にアクセス

変換フック

  • transform: 個々のモジュールのソースコードを変更
  • load: 特定のファイルタイプのカスタムロードロジック
  • resolveId: カスタムモジュール解決
{
  name: 'transform-plugin',
  transform(code, id) {
    if (!id.includes('node_modules') && id.endsWith('.js')) {
      return {
        code: addCustomHeader(code),
        map: null // ソースマップのサポート
      }
    }
  }
}

サーバーとビルドフック

  • configureServer: 開発サーバーにカスタムミドルウェアを追加
  • transformIndexHtml: HTMLファイルを変更
  • writeBundle: バンドルがディスクに書き込まれた後に実行

各フックには特定の戻り値の型と実行タイミングがあります。transformフックはすべてのモジュールに対して実行されるため、高速に保つ必要があります。特定のファイルに対する高コストな操作にはloadを使用してください。

最初のViteプラグインを構築する: 実践例

一般的なパターンを示す3つの実用的なプラグインを構築しましょう。

例1: HTML変更

function htmlPlugin(): Plugin {
  return {
    name: 'html-modifier',
    transformIndexHtml(html) {
      return html.replace(
        '</head>',
        '<script>console.log("Injected!")</script></head>'
      )
    }
  }
}

例2: 開発サーバーミドルウェア

function analyticsPlugin(): Plugin {
  return {
    name: 'request-analytics',
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        console.log(`${req.method} ${req.url}`)
        next()
      })
    }
  }
}

例3: ビルド時のファイル生成

import path from 'path'
import fs from 'fs'
import type { Plugin, ResolvedConfig } from 'vite'

function generateManifest(): Plugin {
  let config: ResolvedConfig
  
  return {
    name: 'generate-manifest',
    configResolved(resolvedConfig) {
      config = resolvedConfig
    },
    writeBundle() {
      const manifestPath = path.join(config.build.outDir, 'manifest.json')
      fs.writeFileSync(manifestPath, JSON.stringify({
        timestamp: Date.now(),
        version: process.env.npm_package_version
      }))
    }
  }
}

デバッグには、フック内でconsole.logを使用して実行をトレースします。this.warn()this.error()メソッドは、例外をスローするよりも優れたエラー報告を提供します。

高度なプラグインパターンとベストプラクティス

仮想モジュール

ディスク上に存在しないモジュールを作成します。

const virtualModuleId = 'virtual:my-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId

export function virtualPlugin(): Plugin {
  return {
    name: 'virtual-plugin',
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
    },
    load(id) {
      if (id === resolvedVirtualModuleId) {
        return `export const msg = "from virtual module"`
      }
    }
  }
}

パフォーマンス最適化

高コストな変換をキャッシュします。

const cache = new Map<string, string>()

function cachedTransform(): Plugin {
  return {
    name: 'cached-transform',
    transform(code, id) {
      if (cache.has(id)) return cache.get(id)
      
      const result = expensiveOperation(code)
      cache.set(id, result)
      return result
    }
  }
}

Viteプラグインのテストには、createServer APIを使用したVitestを使用して、フックの動作を分離して検証します。

プラグインの公開と共有

公開する際は、以下の規約に従ってください。

  1. パッケージ名をvite-plugin-[name]とする
  2. package.jsonのキーワードに"vite-plugin"を含める
  3. Vite固有である理由を文書化する(Vite専用フックを使用している場合)
  4. フレームワーク固有の場合はフレームワークのプレフィックスを追加: vite-plugin-vue-[name]

インストール手順、設定例、APIドキュメントを含む明確なREADMEを作成してください。コミュニティに届けるため、awesome-viteにプラグインを提出しましょう。

まとめ

カスタムViteプラグインを構築することで、ビルドプロセスを完全に制御できます。transformtransformIndexHtmlなどのフックを使用したシンプルな変換から始め、必要に応じてより複雑なパターンに拡張していきましょう。Plugin APIは強力でありながらアプローチしやすく、ほとんどのプラグインは特定の問題を解決するために数個のフックだけで済みます。

より深い探求のために、公式Viteプラグインドキュメントを確認し、エコシステム内の人気のあるプラグインを研究してください。次のビルド最適化やワークフロー改善は、プラグイン一つで実現できるかもしれません。

よくある質問

はい、ほとんどのRollupプラグインは変更なしでViteで動作します。ただし、moduleParsedフックに依存するプラグインや開発サーバー機能が必要なプラグインは、Vite固有の適応が必要です。Vite互換性に関する注意事項については、プラグインのドキュメントを確認してください。

フック内でconsole.logステートメントを使用して実行フローをトレースします。this.warn()とthis.error()メソッドは、より優れたエラー報告を提供します。また、Viteで詳細なログを表示するためにDEBUG環境変数を使用することもできます。

enforceオプションはプラグインの実行順序を制御します。preプラグインはViteコアプラグインの前に実行され、postプラグインは後に実行されます。入力変換にはpreを、出力変更にはpostを使用します。enforceがない場合、プラグインは表示される順序で実行されます。

専門的であっても、プラグインが他の人が直面する可能性のある問題を解決する場合は、公開を検討してください。特定のユースケースを明確に文書化してください。真にプロジェクト固有のプラグインの場合は、ローカルまたはプライベートレジストリに保持してください。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay