别再用插件了,自己写一个才知道 Vite 有多强
你是不是也这样用 Vite?

插件配好了,项目跑起来了,然后就没了。
但你有没有想过:这些插件是怎么工作的?Vite 内部是怎么调度它们的?
今天,带你去 Vite 源码里转一圈。读完这篇,你将学会:
Vite 插件体系的设计原理
所有核心钩子及其执行顺序
手写一个生产级虚拟模块插件
一、Vite 插件体系:站在 Rollup 的肩膀上
Vite 的插件机制,本质上是Rollup 插件 + Vite 特有插件 的组合。
为什么是 Rollup?
因为 Vite 的生产构建用的就是 Rollup。为了复用 Rollup 庞大的插件生态,Vite 直接兼容了 Rollup 的插件接口。你在 Vite 里能用rollup-plugin-xxx,就是这个原因。
但 Vite 不只是 Rollup 的搬运工。它在 Rollup 的基础上,加入了Vite 独有的钩子,专门处理开发服务器的场景。
二、钩子分两类:搞懂执行阶段
Vite 插件的钩子可以分为两大阵营:
阵营 | 执行环境 | 代表钩子 | 作用 |
Build钩子 | 开发+构建 | resolveId、load、transform | 模块解析、加载、转换 |
Output钩子 | 仅构建 | generateBundle、writeBundle | 打包产物生成、输出 |
Vite独有钩子 | 开发服务器 | configureServer、transformIndexHtml | 中间件注入、HTML转换 |
注意:moduleParsed钩子在开发环境不会被调用,Vite 为了性能会避免完整 AST 解析。
三、执行顺序全解析:一张图看懂
当你在vite.config.ts 里配置一堆插件,Vite 是这样调度:
3.1 配置阶段
config钩子:修改配置,所有插件依次执行
configResolved 钩子:配置解析完成,可以读取最终配置

3.2 开发服务器阶段
options钩子(Rollup 兼容)
buildStart钩子:构建开始
对每个模块请求:
resolveId:解析模块路径(返回null 交给下一个插件,返回string 则终止
load:加载模块内容
transform:转换源代码
buildEnd钩子:构建结束
closeBundle钩子:服务器关闭
3.3 Vite 独有钩子

四、手写一个生产级插件:虚拟模块
虚拟模块是 Vite 插件中最实用的模式之一。它让你可以动态生成"不存在"的模块,代码直接 import 进来。
Step 1:定义插件结构

Step 2:使用插件


五、插件执行顺序:enforce 和apply
5.1 enforce:控制执行时机
Vite 插件的执行顺序由 enforce 属性控制:

执行顺序:pre 插件→ Vite 核心插件 → 普通插件 → post 插件
5.2 apply:控制执行环境

六、调试神器:vite-plugin-inspect
写插件时,最难的是调试。不知道哪个插件改了代码,不知道钩子执行顺序。
Vite 官方推荐vite-plugin-inspect:

启动后访问localhost:5173/__inspect/,你可以看到:
每个模块被哪些插件处理过
每个钩子的执行结果
完整的转换链路
最后
Vite 插件体系的核心就三句话:
兼容 Rollup:复用生态,resolveId、load、transform 三件套
扩展 Vite:configureServer、transformIndexHtml 解决开发场景
顺序控制:enforce定前后,apply 定环境
掌握了这三句话,你就能从"用插件的人"变成"写插件的人"。
下次遇到重复性的构建需求,别复制粘贴了。写个插件,一劳永逸。
如果这篇文章对你有帮助,欢迎点赞、在看、转发。
夜雨聆风