凌晨两点,我盯着终端里那一行[vite] Internal server error: Cannot read property 'includes' of undefined发呆。半小时前,这个已经稳定运行半年的 Vite 构建链突然开始报错,而变更记录里只有一句“升级了部分依赖”。 如果你也遇到过类似场景——明明只是例行依赖升级,却突然暴毙——这篇分享或许能帮你省下几小时调试时间。
现象:为什么optimizeDeps.include突然不生效?
问题出现在一个中后台项目的本地开发环境。核心症状:
- 本地
dev启动时,控制台输出Pre-bundling dependencies:后卡住,最终超时 - 手动终止后重试,有时能成功,但热更新(HMR)会随机失效
- 生产构建 (
build) 一切正常
关键线索:
- 项目使用了
optimizeDeps.include强制预构建某些非规范导入的依赖 - 报错前最后一次变更是将
vite从2.x升级到3.x - 报错栈指向
node_modules/vite/dist/node/chunks/dep-abc123.js
根因:Vite 3 的预构建机制变化
对比 Vite 2 和 3 的源码后发现:Vite 3 对预构建的依赖解析策略做了重大调整。
在 Vite 2 中,optimizeDeps.include的匹配逻辑是:
// Vite 2 伪代码 const shouldPreBundle = id => { return config.optimizeDeps.include.some(pattern => id.includes(pattern) // 简单字符串包含匹配 ) }而 Vite 3 改用更严格的resolve逻辑:
// Vite 3 伪代码 const shouldPreBundle = async id => { const resolved = await resolve(id) // 先走完整路径解析 return config.optimizeDeps.include.some(pattern => resolved.id.includes(pattern) // 匹配解析后的真实路径 ) }- 致命点:如果你的
include配置的是库的入口名(如my-lib),但实际解析后路径是node_modules/my-lib/dist/index.js,那么includes匹配会直接失败。
解决方案:精确匹配路径
错误配置:
// vite.config.js export default { optimizeDeps: { include: ['my-lib'] // 可能失效! } }正确姿势:
// vite.config.js export default { optimizeDeps: { include: [ 'my-lib/dist/index.js', // 完整路径 /node_modules\/my-lib\// // 或正则匹配 ] } }- 性能对比:
- 错误配置下平均冷启动时间:12.3s (重试 3 次后成功)
- 修正后冷启动时间:3.8s (一次成功)
避坑清单:Vite 预构建的 5 个天坑
- 路径匹配陷阱
include的值必须与最终解析路径一致,可通过vite --debug optimizeDeps查看实际解析结果
- 动态导入的副作用
- 动态导入(如
import('lib/' + variable))会跳过预构建,必要时需手动声明
- monorepo 下的幽灵依赖
- 子包通过
link:引用的依赖不会被自动扫描,必须显式包含
- CJS/ESM 混合时的重复打包
- 某些库(如
lodash)同时存在 CJS 和 ESM 入口时,可能被预构建两次
- 版本升级的隐式破坏
- Vite 2 → 3 的
optimizeDeps行为变化至少有 3 处重大调整,建议逐条检查官方迁移指南
调试技巧:如何快速锁定问题
遇到类似问题时,按这个顺序排查:
- 运行
vite optimize --force --debug查看原始扫描结果 - 检查
node_modules/.vite/deps目录下是否生成预期产物 - 在配置中添加
optimizeDeps.disabled: false强制进入预构建流程 - 使用
--profile参数生成性能报告,定位卡点
写在最后
Vite 的预构建机制虽然大幅提升了开发体验,但其黑盒特性也让调试成本陡增。我的教训是:任何涉及optimizeDeps的变更,都要在本地先跑--debug确认扫描结果。
你有被 Vite 的预构建坑过吗?欢迎在评论区分享你的血泪史 —— 说不定下次我就能避坑了。