Vite 的插件系统基于 Rollup 插件接口扩展而来,分为通用钩子(build + dev 均可用)和 Vite 专属钩子(dev 专用或特定场景)。
一、钩子执行顺序总览#
Dev 模式(vite dev)#
config → configResolved → configureServer → buildStart
↓ (每次请求)
resolveId → load → transform → configureServer middlewareBuild 模式(vite build)#
config → configResolved → buildStart → resolveId → load
→ transform → moduleParsed → buildEnd → generateBundle
→ writeBundle → closeBundle二、通用钩子(Universal Hooks)#
这些钩子在 dev 和 build 两种模式下都会执行。
1. config#
在解析 Vite 配置之前调用,可用于修改或扩展配置。
{
name: 'my-plugin',
config(config, { command, mode }) {
// command: 'build' | 'serve'
// mode: 'development' | 'production'
if (command === 'build') {
return {
build: {
sourcemap: true
}
}
}
}
}- 返回值:返回的对象会被深度合并进配置
- 执行顺序:按插件顺序执行,支持
async
2. configResolved#
在 Vite 配置解析完成后调用,此时配置已经完全确定,只读。
{
name: 'my-plugin',
configResolved(resolvedConfig) {
// 保存配置以供后续使用
config = resolvedConfig
isBuild = resolvedConfig.command === 'build'
}
}- 典型用途:保存最终配置供其他钩子使用
- 注意:不能在此修改配置
3. buildStart#
构建开始时调用(dev 模式下每次冷启动或模块失效重载时触发)。
{
name: 'my-plugin',
buildStart(options) {
// options 是 Rollup InputOptions
console.log('构建开始,入口:', options.input)
}
}4. resolveId#
解析模块路径,可用于自定义模块解析逻辑(虚拟模块的核心钩子)。
const virtualModuleId = 'virtual:my-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId // 约定加 \0 前缀
{
name: 'virtual-module-plugin',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId // 返回解析后的 id
}
// 返回 null/undefined 表示交给下一个插件处理
}
}- 参数:
(source, importer, options) - 返回
false:表示将该 id 标记为外部模块(external)
5. load#
根据模块 id 加载模块内容,可返回自定义代码。
{
name: 'virtual-module-plugin',
load(id) {
if (id === resolvedVirtualModuleId) {
return `export const msg = "Hello from virtual module!"`
}
}
}- 返回值:字符串(code)或
{ code, map }对象 - 与 resolveId 配合实现虚拟模块
6. transform#
对每个模块的源码进行转换,是最常用的钩子之一。
{
name: 'transform-plugin',
transform(code, id) {
if (!id.endsWith('.vue')) return
// 对代码进行转换
const newCode = code.replace(/console\.log\(.*?\)/g, '')
return {
code: newCode,
map: null // 如有 sourcemap 则返回
}
}
}- 参数:
(code: string, id: string) - 返回 null/undefined:表示不做任何转换
7. buildEnd#
构建结束时调用(不论成功还是失败)。
{
name: 'my-plugin',
buildEnd(error) {
if (error) {
console.error('构建失败:', error)
}
}
}8. closeBundle#
bundle 关闭时调用,是整个构建流程最后一个钩子。
{
name: 'my-plugin',
async closeBundle() {
// 清理临时文件、发送构建完成通知等
await notifyDeploySystem()
}
}三、Build 专属钩子#
1. renderStart#
在 generateBundle 之前、开始渲染 chunk 时调用。
{
renderStart(outputOptions, inputOptions) {
console.log('输出目录:', outputOptions.dir)
}
}2. renderChunk#
对每个输出 chunk 进行转换,类似 transform 但针对的是打包后的 chunk。
{
name: 'banner-plugin',
renderChunk(code, chunk, options) {
return {
code: `/* My Library v1.0 */\n${code}`,
map: null
}
}
}- chunk 信息:包含
chunk.fileName,chunk.imports,chunk.exports等
3. generateBundle#
生成 bundle 文件之前调用,可以增删改 bundle 中的文件。
{
name: 'my-plugin',
generateBundle(options, bundle) {
// bundle 是一个对象,key 是文件名
for (const [fileName, chunk] of Object.entries(bundle)) {
if (fileName.endsWith('.js')) {
// 修改输出内容
chunk.code = chunk.code + '\n// generated by my-plugin'
}
}
// 也可以新增文件
this.emitFile({
type: 'asset',
fileName: 'version.json',
source: JSON.stringify({ version: '1.0.0' })
})
}
}4. writeBundle#
bundle 写入磁盘之后调用(generateBundle 是写入前)。
{
name: 'post-build-plugin',
writeBundle(options, bundle) {
console.log('文件已写入:', options.dir)
// 适合做:压缩、上传 CDN、生成 manifest 等后处理
}
}四、Dev 专属钩子(Vite 独有)#
1. configureServer#
配置开发服务器,可以添加自定义中间件(基于 Connect)。
{
name: 'my-plugin',
configureServer(server) {
// server: ViteDevServer 实例
// 添加中间件(在 Vite 内部中间件之前)
server.middlewares.use('/api/hello', (req, res) => {
res.end(JSON.stringify({ message: 'Hello!' }))
})
// 返回函数 = 在 Vite 中间件之后执行
return () => {
server.middlewares.use((req, res, next) => {
// 后置中间件
next()
})
}
}
}server.moduleGraph:模块依赖图server.watcher:文件监听器(chokidar)server.ws:WebSocket 服务(HMR)
2. configurePreviewServer#
配置 vite preview 预览服务器,用法与 configureServer 相同。
{
name: 'my-plugin',
configurePreviewServer(server) {
server.middlewares.use('/health', (req, res) => {
res.end('OK')
})
}
}3. transformIndexHtml#
转换 index.html 的内容,可注入标签、修改 HTML 结构。
{
name: 'html-plugin',
transformIndexHtml(html) {
return html.replace(
'<title>App</title>',
'<title>My Custom App</title>'
)
}
}
// 高级用法:返回注入描述符
{
name: 'inject-plugin',
transformIndexHtml(html) {
return {
html,
tags: [
{
tag: 'script',
attrs: { src: '/analytics.js', defer: true },
injectTo: 'body' // 'head' | 'body' | 'head-prepend' | 'body-prepend'
},
{
tag: 'meta',
attrs: { name: 'description', content: 'My App' },
injectTo: 'head'
}
]
}
}
}4. handleHotUpdate#
自定义 HMR(热模块替换) 更新处理逻辑。
{
name: 'hmr-plugin',
handleHotUpdate({ file, server, modules, timestamp }) {
if (file.endsWith('.json')) {
// 手动使某些模块失效
const mod = server.moduleGraph.getModuleById('/src/config.ts')
if (mod) {
server.moduleGraph.invalidateModule(mod)
// 发送自定义 HMR 事件
server.ws.send({
type: 'custom',
event: 'config-changed',
data: {}
})
return [] // 返回空数组阻止默认 HMR 行为
}
}
// 返回需要更新的模块列表,不返回则走默认逻辑
}
}五、钩子执行顺序控制#
通过 enforce 属性控制插件执行顺序:
{
name: 'my-plugin',
enforce: 'pre', // 在普通插件之前执行
// enforce: 'post', // 在普通插件之后执行
transform(code, id) { /* ... */ }
}顺序为:pre 插件 → 普通插件 → Vite 内建插件 → post 插件
通过 apply 属性限制执行环境:
{
name: 'build-only-plugin',
apply: 'build', // 只在 build 模式执行
// apply: 'serve', // 只在 dev 模式执行
// 也支持函数形式
apply(config, { command }) {
return command === 'build' && !config.build.ssr
}
}六、完整插件模板#
export default function myPlugin(options = {}) {
let viteConfig
return {
name: 'vite-plugin-my',
enforce: 'pre',
apply: 'build',
// ---- 通用钩子 ----
config(config, env) {},
configResolved(resolved) { viteConfig = resolved },
buildStart(options) {},
resolveId(source, importer) {},
load(id) {},
transform(code, id) {},
buildEnd(error) {},
closeBundle() {},
// ---- Build 专属 ----
renderChunk(code, chunk) {},
generateBundle(options, bundle) {},
writeBundle(options, bundle) {},
// ---- Dev 专属 ----
configureServer(server) {},
transformIndexHtml(html) {},
handleHotUpdate(ctx) {},
}
}七、钩子速查表#
| 钩子 | Dev | Build | 说明 |
|---|---|---|---|
config | ✅ | ✅ | 修改配置 |
configResolved | ✅ | ✅ | 读取最终配置 |
buildStart | ✅ | ✅ | 构建开始 |
resolveId | ✅ | ✅ | 解析模块路径 |
load | ✅ | ✅ | 加载模块内容 |
transform | ✅ | ✅ | 转换模块代码 |
buildEnd | ✅ | ✅ | 构建结束 |
closeBundle | ✅ | ✅ | 关闭 bundle |
renderChunk | ❌ | ✅ | 转换输出 chunk |
generateBundle | ❌ | ✅ | 生成 bundle 前 |
writeBundle | ❌ | ✅ | 写入磁盘后 |
configureServer | ✅ | ❌ | 配置开发服务器 |
configurePreviewServer | ✅* | ❌ | 配置预览服务器 |
transformIndexHtml | ✅ | ✅ | 转换 HTML |
handleHotUpdate | ✅ | ❌ | 自定义 HMR |
configurePreviewServer仅在vite preview时触发
