Contents
📌 目次
- Vite の基本概念とビルドフロー
- プラグイン API 全体像と主要フック解説
- プロジェクト初期化・TypeScript 環境構築
- 最小サンプルプラグインの実装ステップ
- ビルド設定(ライブラリモード)と npm 公開フロー
- デバッグ・テスト・ベストプラクティス
Vite の基本概念とビルドフロー
1️⃣ 開発サーバーの仕組み
| 項目 | 内容 |
|---|---|
| ポイント | ブラウザが ES モジュールを直接取得できるよう、Vite はリクエストごとに オンデマンド で変換・配信します。 |
| 理由 | 従来のバンドラは全ファイルを事前に結合し直すため起動や再ビルドが重いです。一方 Vite は高速トランスパイラ esbuild(C++ 実装)で必要な部分だけ変換し、結果をメモリキャッシュします。 |
| 実行例 | bash npm run dev → 初回アクセス時に対象ファイルが TypeScript → JavaScript に変換され、2 回目以降はキャッシュから即座に返ります。※参考: Qiita「Vite が開発サーバーで ES モジュールを配信する仕組み」 |
2️⃣ 本番ビルドとの違い
| 項目 | 内容 |
|---|---|
| ポイント | vite build は内部で Rollup を呼び出し、全コードを最適化・圧縮したバンドルを生成します。 |
| 理由 | 本番環境ではリクエスト数削減とキャッシュ効率向上が重要です。Rollup のツリーシェイキングやコード分割はこの目的に最適です。 |
| 出力例 | npm run build → dist/ に以下が生成されます。• ESM( *.js)• IIFE(旧ブラウザ向け、@vitejs/plugin-legacy が自動生成) ※参考: Techmania「Vite で本番ビルドを作るときのポイント」 |
プラグイン API 全体像と主要フック解説
Vite のプラグインは Rollup と同様に フック関数 で構成されます。ここでは実務で頻出するフックを中心に、正しい型情報とサンプルコードを掲載します。
config フック
- 目的: Vite 本体が設定オブジェクトを確定する前に、プラグイン側からデフォルトやユーザー指定のオプションを注入できる。
- 注意点: 返り値は必ず新しい設定オブジェクトか
Object.assignでマージしたものにしてください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// src/plugins/config-inject.ts import type { UserConfig } from 'vite'; export default function configInject(opts?: { prefix: string }) { return { name: 'config-inject', config(config: UserConfig) { const define = { __PREFIX__: JSON.stringify(opts?.prefix ?? 'HELLO') }; return { define }; } }; } |
resolveId / load フック
- 目的: 仮想モジュールやエイリアスの実装に使用。Vite がファイルシステム上に存在しない ID を要求したときに呼び出されます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// src/plugins/virtual-hello.ts export default function virtualHello() { return { name: 'virtual-hello', resolveId(id: string) { if (id === 'virtual:hello') return id; // 解決成功を示す }, load(id: string) { if (id === 'virtual:hello') return `export const msg = "👋 from virtual module";`; } }; } |
transform フック
- 目的: 取得したコードを任意の形に加工。Babel・SWC のような本格的トランスパイルだけでなく、軽微な文字列置換やコメント除去にも使える。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// src/plugins/strip-console.ts export default function stripConsole() { return { name: 'strip-console', transform(code: string, id: string) { if (/\.tsx?$/.test(id)) { const transformed = code.replace(/console\.\w+\(.*?\);?/g, ''); return { code: transformed, map: null }; } } }; } |
generateBundle フック
- 目的: Rollup がバンドルを生成した直後に呼び出され、アセットの追加・ファイル名変更が可能。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// src/plugins/embed-banner.ts export default function embedBanner() { return { name: 'embed-banner', generateBundle(_options, bundle) { this.emitFile({ type: 'asset', fileName: 'banner.txt', source: 'Built with Vite Plugin – ©2026' }); } }; } |
プロジェクト初期化・TypeScript 環境構築
1️⃣ npm 初期化と Vite のインストール
⚠️ 注意:執筆時点(2026‑04‑22)の最新安定版は v5.3.0。
vite@latestが自動でこのバージョンを指すとは限らないので、明示的にバージョン指定することを推奨します。
|
1 2 3 4 5 6 7 8 9 |
# プロジェクト作成 npm init -y # Vite と開発依存をインストール(v5 系) npm install vite@5.3.0 --save-dev # TypeScript 関連パッケージ npm install typescript @types/node --save-dev |
2️⃣ tsconfig.json のベストプラクティス
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "compilerOptions": { // Vite 本体が ESM 前提なので ESNext を指定 "module": "ESNext", "target": "ES2022", // ライブラリ利用者向けに型定義を自動生成 "declaration": true, "sourceMap": true, "strict": true, "esModuleInterop": true, "skipLibCheck": true, // 出力先はビルド時の dist ディレクトリだけに限定 "outDir": "./dist" }, "include": ["src/**/*.ts"] } |
declaration: true→.d.tsが自動生成され、npm 公開時に同梱できます。"moduleResolution": "bundler"(省略可)を付与すると Vite のモジュール解決と一致します。
3️⃣ vite.config.ts にプラグインを組み込む雛形
|
1 2 3 4 5 6 7 |
import { defineConfig } from 'vite'; import helloPlugin from './src/vite-plugin-hello'; export default defineConfig({ plugins: [helloPlugin({ prefix: '🚀' })], }); |
最小サンプルプラグインの実装ステップ
📁 ディレクトリ構成(例)
|
1 2 3 4 5 6 7 8 9 |
my-vite-plugin/ ├─ src/ │ └─ vite-plugin-hello.ts ├─ tests/ │ └─ plugin.test.ts ├─ package.json ├─ tsconfig.json └─ vite.config.ts |
1️⃣ src/vite-plugin-hello.ts(全フック実装例)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import type { Plugin } from 'vite'; export interface HelloOptions { /** メッセージに付与するプレフィックス */ prefix?: string; } /** * vite-plugin-hello – Vite 5.3 用最小サンプルプラグイン * * - config → define 定数注入 * - resolveId / load → 仮想モジュール `virtual:hello` * - transform → .js ファイルのコメント除去 * - generateBundle → ビルド成果物にテキストアセット追加 */ export default function helloPlugin(opts: HelloOptions = {}): Plugin { const prefix = opts.prefix ?? '👋'; return { name: 'vite-plugin-hello', // ---- config ------------------------------------------------- config() { return { define: { __HELLO_PREFIX__: JSON.stringify(prefix) } }; }, // ---- resolveId ----------------------------------------------- resolveId(id) { if (id === 'virtual:hello') return id; }, // ---- load ---------------------------------------------------- load(id) { if (id === 'virtual:hello') return `export const msg = "${prefix} from virtual module";`; }, // ---- transform ----------------------------------------------- transform(code, id) { if (/\.js$/.test(id)) { const cleaned = code.replace(/\/\/.*$/gm, ''); return { code: cleaned, map: null }; } }, // ---- generateBundle ------------------------------------------ generateBundle() { this.emitFile({ type: 'asset', fileName: 'hello.txt', source: `${prefix} – built by vite-plugin-hello`, }); }, }; } |
2️⃣ package.json のビルドスクリプト(ライブラリモード)
Vite のデフォルト vite build は「アプリ」向けです。プラグインを npm パッケージ として配布する場合は、Rollup の library mode を明示します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "name": "vite-plugin-hello", "version": "0.1.0", "type": "module", // ESM デフォルトに設定 "main": "./dist/index.cjs.js", // CommonJS エントリ(Node が利用可能) "module": "./dist/index.esm.js", // ES Module エントリ "types": "./dist/index.d.ts", "files": ["dist"], // 公開対象を dist のみ限定 "scripts": { "build": "vite build --config vite.config.lib.ts", "test": "vitest run" }, "devDependencies": { "vite": "^5.3.0", "typescript": "^5.4.2", "vitest": "^1.0.0" } } |
vite.config.lib.ts(ライブラリビルド用設定)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { defineConfig } from 'vite'; import helloPlugin from './src/vite-plugin-hello'; export default defineConfig({ plugins: [helloPlugin()], build: { lib: { // エントリポイント entry: './src/vite-plugin-hello.ts', name: 'VitePluginHello', fileName: (format) => `vite-plugin-hello.${format}.js`, }, rollupOptions: { // 外部化しておくとバンドルサイズが小さくなる external: ['vite'], output: { globals: { vite: 'vite' } } } } }); |
ポイント
-build.libを指定しないとvite buildは SPA 用の出力になるため、プラグインとして利用できません。
-external: ['vite']により Vite 本体はバンドルに含めず、利用側が同梱した Vite とリンクします。
3️⃣ 実装確認(開発サーバーでのテスト)
|
1 2 |
npm run dev # vite.config.ts が読み込まれ、プラグインが有効になる |
src/main.ts に以下を書けば動作を目視できます。
|
1 2 3 |
import { msg } from 'virtual:hello'; console.log(msg); // => "🚀 from virtual module" |
ビルド設定(ライブラリモード)と npm 公開フロー
1️⃣ ライブラリビルドのベストプラクティス
| 設定項目 | 推奨値 / 説明 |
|---|---|
build.lib.entry |
プラグイン本体のエントリ(例: src/vite-plugin-hello.ts) |
build.lib.name |
UMD 形式で出力する場合に必要な名前。ESM のみなら不要 |
build.lib.fileName |
フォーマット別ファイル名 (index.esm.js, index.cjs.js) |
rollupOptions.external |
vite や他の peerDependencies を除外し、バンドルサイズを削減 |
esbuild.minify |
本番用に true にするとコードが圧縮されます(デフォルトは有効) |
参考: Vite 公式ドキュメント「Building a Library」
2️⃣ npm 公開手順
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# 1. ビルド成果物を生成 npm run build # 2. パッケージ内容を確認(dist のみが含まれるか) npm pack --dry-run # 3. バージョンを更新(SemVer に従う) npm version patch # または minor / major # 4. 公開 npm publish --access public |
必要な package.json フィールド例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "type": "module", "files": ["dist"], "publishConfig": { "registry": "https://registry.npmjs.org/" }, "repository": { "type": "git", "url": "git+https://github.com/yourname/vite-plugin-hello.git" }, "bugs": { "url": "https://github.com/yourname/vite-plugin-hello/issues" }, "homepage": "https://github.com/yourname/vite-plugin-hello#readme" } |
デバッグ・テスト・ベストプラクティス
🔧 デバッグテクニック
| 方法 | 説明 |
|---|---|
this.error / this.warn |
Vite のロガーに統合されたメッセージ。ビルドを中断したいときは this.error('msg') を使用。 |
debug パッケージ |
環境変数 DEBUG=vite-plugin-hello* で詳細ログ出力が可能。 |
--watch オプション |
プラグイン開発中に自動リビルドさせるため、npm run dev -- --watch と指定。 |
例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import debug from 'debug'; const log = debug('vite-plugin-hello'); export default function helloPlugin() { return { name: 'vite-plugin-hello', configResolved(config) { log('Vite mode: %s', config.mode); }, transform(code, id) { if (id.endsWith('.js')) this.warn(`Transforming ${id}`); return null; } }; } |
実行例:
|
1 2 |
DEBUG=vite-plugin-hello* npm run dev |
✅ ユニットテストと E2E テスト
Vitest(ユニットテスト)設定
vitest.config.ts
|
1 2 3 4 5 6 7 8 9 10 11 |
import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { globals: true, environment: 'node', include: ['tests/**/*.test.ts'], coverage: { provider: 'c8' } }, }); |
tests/plugin.test.ts
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import { createServer } from 'vite'; // 正しい API(内部の createViteServer は非公開) import helloPlugin from '../src/vite-plugin-hello'; test('virtual module resolves correctly', async () => { const server = await createServer({ plugins: [helloPlugin({ prefix: 'TEST' })], logLevel: 'silent', }); // Vite の内部モジュールグラフにアクセス const mod = await server.moduleGraph.idToModuleMap.get('virtual:hello')?.load(); expect(mod).toContain('TEST from virtual module'); }); |
Playwright(E2E)例
|
1 2 3 |
npm i -D @playwright/test npx playwright test |
e2e/hello.spec.ts
|
1 2 3 4 5 6 7 8 |
import { test, expect } from '@playwright/test'; test('plugin‑generated message appears', async ({ page }) => { await page.goto('http://localhost:5173'); // vite dev server が起動中であること前提 const text = await page.textContent('#msg'); expect(text).toBe('🚀 from virtual module'); }); |
📚 実務でのベストプラクティスまとめ
| 項目 | 推奨アクション |
|---|---|
| 型定義 | export type PluginOptions = {...} を公開し、declaration: true で .d.ts を生成。 |
| エラーハンドリング | 致命的エラーは this.error('…') → ビルドを即中断させる。 |
| パフォーマンス最適化 | 重い処理は transformIndexHtml や generateBundle に限定し、transform は軽微な文字列置換に留める。 |
| キャッシュ活用 | this.cache.set(key, value) / this.cache.get(key) で仮想モジュールの結果を再利用。 |
| ドキュメント整備 | README に インストール手順、vite.config の使用例、API 表 を必ず掲載。GitHub Actions で CI → npm test && npm run build が成功することを必須に。 |
| CI/CD | - npm run lint(ESLint + Prettier)- npm run type-check (tsc --noEmit) - npm run test:coverage (Vitest カバレッジ)- npm publish 前に npm pack でパッケージ内容を検証 |
🎉 まとめ
- Vite の開発サーバーはオンデマンド変換とキャッシュで即時フィードバックを実現。
- 本番ビルドは Rollup ベースの最適化パイプラインに切り替わり、コード分割・ツリーシェイキングが行われる。
- プラグイン APIは
config→resolveId/load→transform→generateBundleの流れで実装すれば、ほとんどの拡張要件をカバーできる。 - ライブラリモードでビルドしないと npm 公開時に不要な SPA 用バンドルが生成されてしまうので、必ず
build.libを設定すること。 - テスト・デバッグは Vite が提供する
createServer(内部 API ではなく公開 API)やdebugパッケージを活用し、CI に組み込んで品質を担保する。
この記事を読んだら、ぜひ自分だけの Vite プラグインを作って GitHub に公開し、
#vite-plugin-challengeでシェアしてください!
外部参照(2026‑04‑22 時点)
| 内容 | URL |
|---|---|
| Vite 公式ドキュメント – ビルド (ライブラリモード) | https://vitejs.dev/guide/build.html#library-mode |
| Qiita 記事 – Vite が開発サーバーで ES モジュールを配信する仕組み | https://qiita.com/your-id/items/vite-dev-server |
| Techmania 記事 – Vite の本番ビルドのポイント | https://techmania.jp/articles/vite-build |
Vite GitHub リポジトリ – createServer API 実装例 |
https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server.ts |
| Vitest 公式ガイド – テスト設定と実行方法 | https://vitest.dev/config/ |
| Playwright 公式ドキュメント – E2E テスト入門 | https://playwright.dev/docs/intro |
本稿は執筆時点の情報に基づいています。Vite のメジャーバージョンが上がるたびに API や設定方法が変わる可能性がありますので、常に公式サイトで最新情報をご確認ください。