我用一个面板找出构建慢的根因:vite-plugin-inspect 实战诊断
我做了什么
我一直想知道一个问题:Vite 构建慢的时候,到底是哪个插件在拖后腿?
这个问题的难处在于——一个 Vite 项目的vite.config.ts可能挂了 20+ 个插件。手动排查的话,逐个注释插件、重启服务、计时对比,插件之间有依赖关系,注释 A 要连带注释 B,非常折腾。
vite-plugin-inspect 就是为这个问题设计的。我搭建了一个测试仓库,放了 15 个插件(其中 3 个是模拟的"慢插件"),用它完整走了一遍性能诊断流程。
搭建测试场景
为了贴近真实情况,我构造了三种慢场景:
- 一个对所有文件做 AST 解析的插件(模拟没有文件过滤的自研插件)
- 一个 Vite 生命周期内被重复调用的插件(模拟 HMR 边界重算的副作用)
- 一个 resolveId 逻辑有性能问题的插件(模拟 glob 扫描开销)
对应的vite.config.ts大概长这样:
importInspectfrom'vite-plugin-inspect'exportdefaultdefineConfig({plugins:[Inspect(),// 就这一行,零配置slowAstPlugin(),// 模拟:无过滤的 AST 解析repeatedTransform(),// 模拟:重复调用的 transformslowResolveId(),// 模拟:glob 扫描// ... 其余 12 个正常插件]})仓库里有 50 个模拟模块文件(.tsx/.vue/.mdx),加上 node_modules 里的依赖,总共会被 Vite 处理的模块约 180 个。pnpm dev冷启动稳定在12 秒左右,足够复现慢场景。
诊断三步走
第一步:Plugin Metrics 面板
启动后打开http://localhost:5173/__inspect/,点Plugin Metrics面板。一个柱状图把所有插件的 transform 总耗时从高到低排列出来。
立即锁定了三号嫌疑:
| 插件 | transform 总耗时 | 调用次数 |
|---|---|---|
slow-ast-plugin | 5,420ms | 180 |
vite:vue | 2,100ms | 50 |
repeated-transform | 1,840ms | 380 ⚠️ |
unplugin-auto-import | 890ms | 15 |
slow-resolve-id | 760ms | 45 |
两条线索:
slow-ast-plugin总耗时最高,但调用 180 次——是每个模块都被处理了吗?repeated-transform调用次数 380——180 个模块,怎么多了一倍?
第二步:点进个别模块看 transform 链
在Modules面板里搜索一个 .tsx 模块,点进去看它的完整 transform 链路。面板右侧用 CodeMirror 做了 diff 对比——左侧是原始代码,右侧是每一步 transform 后的代码。
这个模块的 transform 链条是这样的:
__load__ (虚拟步骤,原始文件内容) vite:pre-alias (代码无变化 ✓) slow-ast-plugin (代码变化,耗时 420ms) repeated-transform (代码无变化,耗时 180ms) ⚠️ repeated-transform (代码无变化,耗时 175ms) ⚠️ ← 重复调用! vite:vue (无变化) vite:esbuild (最终编译)repeated-transform对同一个模块被调用了两次,但两次的输出完全一致——diff 面板全绿,一行没变。这解释了为什么它的调用总数是模块数的两倍。
slow-ast-plugin的问题更直接:它对自己不应该处理的文件也过了完整的 AST 解析流程——node_modules里的.js文件、纯 CSS 文件,都触发了解析。
第三步:反向验证
为了确认诊断结果,我给slow-ast-plugin加了文件过滤:
// 修复前plugin.transform=(code,id)=>{returnparseAndTransform(code)// 所有文件都处理}// 修复后plugin.transform=(code,id)=>{constVALID_EXTENSIONS=/\.(tsx|vue|mdx)$/if(!VALID_EXTENSIONS.test(id)||id.includes('node_modules'))returnreturnparseAndTransform(code)}给repeated-transform加了去重:
constseen=newSet<string>()plugin.transform=(code,id)=>{constkey=`${id}::${simpleHash(code)}`if(seen.has(key))returnseen.add(key)// ... 实际逻辑}重新pnpm dev:
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 冷启动时间 | 12.1s | 5.3s |
slow-ast-plugintransform 总耗时 | 5,420ms | 1,180ms |
repeated-transform调用次数 | 380 | 180 |
repeated-transformtransform 总耗时 | 1,840ms | 350ms |
改动共约 20 行代码,启动时间砍掉一半。不是因为优化了算法,而是——插件根本不需要处理那么多模块。
两个我踩到的 API 细节
整个诊断过程中,vite-plugin-inspect 有两个细节让我更信任它的数据:
1.__load__虚拟步骤
每个模块的 transform 链条第一步都是一个名为__load__的虚拟步骤——它记录的是文件系统的原始内容。有了它,Diff 面板才能展示"原始文件 vs 第一步 transform"的差异。这个步骤不参与耗时统计(start === end),纯粹是为了 Diff 对齐。
如果你自己实现类似的工具,这个"第零步锚点"是个很讨巧的设计——不是 hack,而是数据模型的自然表达。
2. Plugin Metrics 的时间单位
柱状图里的数字是所有模块该插件 transform 的累加耗时,不是"每个模块的平均耗时"。这意味着如果一个插件的 transform 非常快(单次 < 5ms),但被调用了 1000 次——它在柱状图里依然可能很显眼。所以看柱状图时要同时看调用次数,不要被绝对数值误导。
如果手上没有 vite-plugin-inspect
我试着在没有工具的情况下手动排查,结论是:能做,但效率差 10 倍。
手动流程大概是:
- 注释一半插件 → 看启动时间变没变 → 二分法缩小范围
- 给疑似慢插件加
console.time/console.timeEnd - 在模块源码里加
performance.now()打点
但插件之间有依赖时,二分法会碰到"注释 A 后 B 报错"的问题。而且手动计时不够精确——console.time拿不到"这个模块被哪些插件处理过、每次耗时是多少"的全景。
vite-plugin-inspect 的核心价值是:一次接入,全局透视;不改任何被监控插件的代码;数据粒度到单个模块的单次 transform。这三样合在一起,手动方案做不到。
这个诊断能力的通用性
这套流程不只适用于"找出慢插件"这一个问题。同类场景还包括:
- 新接手的项目:不了解每个插件做了什么,看 Modules 面板能直观看出模块的转换链路
- 升级 Vite 大版本后:对比升级前后 Plugin Metrics 的耗时变化,定位兼容性问题
- 接入新插件后启动变慢:不用猜,面板直接告诉你哪个新插件多了多少耗时