一、为什么要学 Vite 插件开发?
在使用 Vite 的过程中,你可能会遇到这些场景:
- 需要在构建时自动生成某些文件
- 想要自定义模块解析逻辑
- 需要在开发服务器中添加特定的 API 路由
- 想要集成特定的代码检查或转换工具
Vite 插件就是解决这些问题的钥匙!
Vite 插件 vs Rollup 插件
Vite 的插件系统基于 Rollup,但做了大量扩展:
| 特性 | Rollup 插件 | Vite 插件 |
|---|---|---|
| 构建阶段 | ✅ 支持 | ✅ 支持 |
| 开发服务器 | ❌ 不支持 | ✅ 支持 |
| HMR 热更新 | ❌ 不支持 | ✅ 支持 |
| 配置解析 | ❌ 不支持 | ✅ 支持 |
二、插件基础:Hello World
Vite 插件本质上是一个对象(或返回对象的函数),包含name属性和各种钩子函数:
2.1 最简单的插件
// my-first-plugin.js export default function myFirstPlugin() { return { name: 'my-first-plugin', // 配置解析时调用 config(config, { command }) { console.log('🚀 Vite 模式:', command); // 'serve' 或 'build' return { // 可以返回部分配置,会与用户配置合并 base: command === 'build' ? '/app/' : '/' }; }, // 构建开始时调用 buildStart() { console.log('📦 开始构建...'); }, // 构建结束时调用 buildEnd() { console.log('✅ 构建完成!'); } }; }使用插件:
// vite.config.js import { defineConfig } from 'vite'; import myFirstPlugin from './my-first-plugin.js'; export default defineConfig({ plugins: [myFirstPlugin()] });2.2 运行效果
$ npm run dev 🚀 Vite 模式: serve 📦 开始构建... ✅ 构建完成! VITE v5.0.0 ready in 320 ms三、核心钩子详解
Vite 提供了丰富的钩子函数,覆盖了从配置解析到构建完成的整个生命周期。
3.1 配置相关钩子
export default function configPlugin() { return { name: 'config-plugin', // 1. config - 修改或扩展配置 config(userConfig, { command, mode }) { // command: 'serve' | 'build' // mode: 'development' | 'production' | 自定义 return { resolve: { alias: { '@': '/src' } } }; }, // 2. configResolved - 配置解析完成后 configResolved(resolvedConfig) { // 可以获取最终解析后的配置 console.log('📁 项目根目录:', resolvedConfig.root); console.log('🔧 运行模式:', resolvedConfig.mode); } }; }3.2 开发服务器钩子
这是 Vite 独有的,Rollup 插件无法使用:
export default function serverPlugin() { return { name: 'server-plugin', // 配置开发服务器 configureServer(server) { // server 是 ViteDevServer 实例 // 添加自定义路由 server.middlewares.use('/api/health', (req, res, next) => { res.end(JSON.stringify({ status: 'ok', time: Date.now() })); }); // 监听文件变化 server.watcher.on('change', (file) => { console.log('📝 文件变化:', file); }); }, // 配置预览服务器(vite preview) configurePreviewServer(server) { server.middlewares.use('/api/version', (req, res) => { res.end(JSON.stringify({ version: '1.0.0' })); }); } }; }3.3 构建钩子(Rollup 兼容)
export default function buildPlugin() { return { name: 'build-plugin', // 解析模块ID resolveId(source, importer) { if (source === 'virtual-module') { return source; // 返回解析后的ID } }, // 加载模块内容 load(id) { if (id === 'virtual-module') { return 'export const msg = "Hello from virtual module!"'; } }, // 转换代码 transform(code, id) { // 只对 .js 文件进行处理 if (id.endsWith('.js')) { // 简单的代码转换示例 return { code: code.replace(/console\\.log/g, 'console.debug'), map: null // 可以返回 source map }; } } }; }3.4 完整生命周期图
config → configResolved → configureServer ↓ (开发服务器运行中) ↓ buildStart → resolveId → load → transform ↓ (HMR 触发时重复) ↓ buildEnd → closeBundle四、实战案例
案例 1:自动生成环境信息文件
开发中经常需要知道当前构建的时间、版本号等信息:
// plugins/build-info-plugin.js import { writeFileSync } from 'fs'; import { resolve } from 'path'; export default function buildInfoPlugin() { return { name: 'build-info', buildStart() { const info = { version: process.env.npm_package_version || '1.0.0', buildTime: new Date().toISOString(), nodeEnv: process.env.NODE_ENV, platform: process.platform }; // 将信息写入 JSON 文件 const outputPath = resolve(process.cwd(), 'public', 'build-info.json'); writeFileSync(outputPath, JSON.stringify(info, null, 2)); console.log('📝 构建信息已生成'); } }; }使用:
// vite.config.js import buildInfoPlugin from './plugins/build-info-plugin.js'; export default { plugins: [buildInfoPlugin()] };前端获取:
// App.vue 或任意 JS 文件 fetch('/build-info.json') .then(r => r.json()) .then(info => { console.log('📦 版本:', info.version); console.log('🕐 构建时间:', info.buildTime); });案例 2:Mock API 服务器
开发时常用的 Mock 数据插件:
// plugins/mock-plugin.js import { readFileSync } from 'fs'; import { resolve } from 'path'; const mockData = { '/api/users': [ { id: 1, name: '张三', role: 'admin' }, { id: 2, name: '李四', role: 'user' } ], '/api/posts': [ { id: 1, title: 'Hello Vite', content: 'Vite 真快!' } ] }; export default function mockPlugin(options = {}) { const { prefix = '/api', delay = 500 } = options; return { name: 'mock-server', configureServer(server) { server.middlewares.use((req, res, next) => { // 只处理 API 请求 if (!req.url.startsWith(prefix)) { return next(); } // 模拟网络延迟 setTimeout(() => { const data = mockData[req.url]; if (data) { res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ code: 0, data })); } else { res.statusCode = 404; res.end(JSON.stringify({ code: 404, msg: 'Not Found' })); } }, delay); }); } }; }使用:
// vite.config.js import mockPlugin from './plugins/mock-plugin.js'; export default { plugins: [ mockPlugin({ prefix: '/api', delay: 300 // 模拟 300ms 延迟 }) ] };案例 3:条件编译(类似 #ifdef)
实现类似 C 语言的条件编译功能:
// plugins/conditional-compile-plugin.js export default function conditionalCompilePlugin(options = {}) { const { env = {} } = options; return { name: 'conditional-compile', transform(code, id) { // 只对 JS/TS/Vue 文件处理 if (!/\\.(js|ts|vue)$/.test(id)) return; // 匹配 //#ifdef KEY ... //#endif const regex = /\\/\\/\\s*#ifdef\\s+(\\w+)\\s*([\\s\\S]*?)\\/\\/\\s*#endif/g; return { code: code.replace(regex, (match, key, content) => { // 如果环境变量存在且为真,保留内容,否则删除 return env[key] ? content : ''; }), map: null }; } }; }使用:
// vite.config.js import conditionalCompile from './plugins/conditional-compile-plugin.js'; export default { plugins: [ conditionalCompile({ env: { DEBUG: process.env.NODE_ENV === 'development', PRO_FEATURE: false } }) ] };源代码中:
function initApp() { //#ifdef DEBUG console.log('调试模式启动'); console.log('配置信息:', config); //#endif //#ifdef PRO_FEATURE loadProModules(); //#endif startApp(); }构建后(DEBUG=true, PRO_FEATURE=false):
function initApp() { console.log('调试模式启动'); console.log('配置信息:', config); startApp(); }五、进阶技巧
5.1 虚拟模块
创建不对应真实文件的模块:
// plugins/virtual-module-plugin.js const virtualModuleId = 'virtual:app-config'; const resolvedVirtualModuleId = '\\0' + virtualModuleId; export default function virtualModulePlugin() { return { name: 'virtual-module', resolveId(id) { if (id === virtualModuleId) { return resolvedVirtualModuleId; } }, load(id) { if (id === resolvedVirtualModuleId) { return ` export const appName = 'My Awesome App'; export const version = '${process.env.npm_package_version}'; export const features = { darkMode: true, i18n: true }; `; } } }; }使用:
import { appName, version, features } from 'virtual:app-config'; console.log(appName); // 'My Awesome App'5.2 HMR 热更新支持
让插件支持热更新:
export default function hmrPlugin() { return { name: 'hmr-plugin', handleHotUpdate({ server, modules, timestamp }) { // 自定义 HMR 处理逻辑 // 过滤特定模块 const filtered = modules.filter(m => !m.id.includes('node_modules')); console.log('🔄 热更新模块:', filtered.map(m => m.id)); // 返回模块列表,Vite 会继续处理 return filtered; } }; }5.3 插件排序
控制插件执行顺序:
export default { plugins: [ { ...myPlugin(), enforce: 'pre' // 'pre' | 'post',默认 normal } ] };六、发布你的插件
6.1 插件项目结构
vite-plugin-awesome/ ├── src/ │ └── index.js # 插件入口 ├── package.json ├── README.md └── LICENSE6.2 package.json 配置
{ "name": "vite-plugin-awesome", "version": "1.0.0", "description": "An awesome Vite plugin", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", "files": ["dist"], "scripts": { "build": "rollup -c", "prepublishOnly": "npm run build" }, "peerDependencies": { "vite": "^4.0.0 || ^5.0.0" }, "keywords": ["vite", "plugin", "vite-plugin"], "license": "MIT" } 6.3 发布到 npm6.3 发布到 npm
npm login npm publish七、常用插件推荐
| 插件 | 功能 |
|---|---|
@vitejs/plugin-vue | Vue 单文件组件支持 |
@vitejs/plugin-react | React Fast Refresh |
vite-plugin-pwa | PWA 支持 |
unplugin-auto-import | 自动导入 API |
vite-plugin-svg-icons | SVG 图标雪碧图 |
vite-plugin-mock | Mock 数据服务 |
八、总结
Vite 插件开发并不复杂,核心要点:
✅必须掌握:
- 插件基本结构(name + hooks)
- 常用钩子:
config、transform、configureServer - 虚拟模块的使用
✅进阶技能:
- HMR 热更新处理
- 开发服务器中间件
- 构建流程控制
✅最佳实践:
- 插件选项设计要灵活
- 提供详细的文档和示例
- 处理好错误边界