插件系统与钩子
约 781 字大约 3 分钟
2026-02-25
Vite 插件系统是它工程扩展能力的核心。很多“项目特有需求”(如虚拟模块、按需注入、构建产物改写)都通过插件实现。
为什么需要插件
当默认配置项(resolve/server/build/css)无法覆盖需求时,插件可以在“解析 -> 加载 -> 转换 -> 产物输出”的各阶段插入逻辑。
常见场景:
- 接管某类文件加载规则(如虚拟模块、远程模块)。
- 代码转换(如自动注入、宏替换、调试标识)。
- 开发服务器增强(中间件、接口 mock、HMR 定制)。
- 构建后产物处理(文件重命名、清单输出、上报)。
插件基本语法
一个 Vite 插件本质上是一个对象,最少包含 name,常配合若干钩子函数。
import type { Plugin } from 'vite'
export function demoPlugin(): Plugin {
return {
name: 'demo-plugin',
}
}常用钩子
| 钩子 | 阶段 | 作用 | 典型用途 |
|---|---|---|---|
config | 配置解析前 | 修改/合并用户配置 | 注入默认 alias、define |
configResolved | 配置解析后 | 读取最终配置 | 区分 dev/prod 行为 |
configureServer | dev server 启动 | 注入中间件或 ws 逻辑 | mock、健康检查、调试接口 |
resolveId | 模块解析 | 拦截或重写路径 | 虚拟模块、别名扩展 |
load | 模块加载 | 返回模块源码 | 生成虚拟模块内容 |
transform | 代码转换 | 改写源码并返回 map | 自动注入代码、语法替换 |
handleHotUpdate | HMR 更新 | 自定义热更新行为 | 精准刷新、跨文件联动 |
buildEnd / closeBundle | 构建结束 | 收尾逻辑 | 上报构建信息、清理文件 |
从 0 到 1:一个可运行插件
下面这个插件做三件事:
- 提供虚拟模块
virtual:app-meta - 注入构建时间与模式
- 在开发期暴露一个调试接口
plugins/app-meta.ts
import type { Plugin, ResolvedConfig } from 'vite'
export function appMetaPlugin(): Plugin {
let config: ResolvedConfig
return {
name: 'app-meta-plugin',
// 读取最终配置
configResolved(resolved) {
config = resolved
},
// 拦截模块解析,把虚拟模块映射到内部 id
resolveId(id) {
if (id === 'virtual:app-meta') {
return '\0virtual:app-meta'
}
return null
},
// 生成虚拟模块源码
load(id) {
if (id === '\0virtual:app-meta') {
return `
export const mode = ${JSON.stringify(config.mode)}
export const buildTime = ${JSON.stringify(new Date().toISOString())}
`
}
return null
},
// 开发服务器增强:增加调试接口
configureServer(server) {
server.middlewares.use('/__debug/meta', (req, res) => {
res.setHeader('Content-Type', 'application/json')
res.end(
JSON.stringify({
mode: config.mode,
command: config.command,
root: config.root,
})
)
})
},
}
}vite.config.ts
import { defineConfig } from 'vite'
import { appMetaPlugin } from './plugins/app-meta'
export default defineConfig({
plugins: [appMetaPlugin()],
})src/main.ts
import { mode, buildTime } from 'virtual:app-meta'
console.log('mode:', mode)
console.log('buildTime:', buildTime)钩子执行顺序(简化理解)
config:读取并合并配置。configResolved:得到最终配置。- dev 时每次请求会经历
resolveId -> load -> transform。 - 发生文件变更时触发
handleHotUpdate。 - build 完成后触发
buildEnd/closeBundle。
使用建议
插件开发中的高频问题
resolveId/load/transform里忘记return null,会误伤其他模块。- 在
transform里进行过重计算,导致 HMR 明显变慢。 - 开发与构建逻辑混在一起,调试困难。建议通过
config.command区分。 - 插件顺序不明确,多个插件改同一段代码时容易相互覆盖。
