最近在项目执行流水线时,突然遇到一个诡异的构建错误:
[vite:esbuild-transpile] Transform failed with 1 error:
assets/vant-!~{00b}~.js:289:6: ERROR: The symbol "bem" has already been declared
第一眼看到这个错误,我本能地以为是自己代码里重复定义了 bem 变量,或者是 Vant 组件库的问题。但排查一圈后发现——问题根源竟然是 rollup 包的版本升级导致的构建作用域异常!
本文将带你完整还原问题现场、分析根本原因,并提供可落地的解决方案,希望能帮助到同样被“幽灵报错”困扰的你。
🔍 一、问题现象
项目使用的是:
vite@5.4.1vant@3.4.5
执行 npm run build 后(或 CI 环境重新安装依赖并构建),控制台报错:
[vite:esbuild-transpile] Transform failed with 1 error:
assets/vant-!~{00b}~.js:289:6: ERROR: The symbol "bem" has already been declared
奇怪的是:
- 本地昨天还好好的;
- 其他同事也遇到了同样的问题;
- 错误文件名看起来像虚拟生成的(
vant-!~{00b}~.js); - 搜索项目代码,根本没有手动定义过
bem;
直觉告诉我:这不是代码问题,而是构建工具链的依赖出了问题。
🕵️♂️ 二、初步排查方向
我首先怀疑以下几个常见原因:
| 排查项 | 结果 |
|---|---|
是否自己定义了 const bem = ...? | ❌ 没有 |
| 是否多次引入 Vant 组件? | ❌ 正常按需引入 |
清除 .vite 缓存是否解决? | ❌ 无效 |
升级 vite 和 vant 到最新版? | ❌ 仍报错 |
此时,我开始怀疑是不是 Vite 预构建(pre-bundling)阶段出错了。
🔬 三、深入分析:从 node_modules/.vite 找线索
进入 node_modules/.vite 目录,找到报错文件 vant-!~{00b}~.js,定位到第 289 行附近,发现了这样的代码片段:
Js
深色版本
const bem = (block, element, modifier) => { /* ... */ };
// ... 中间省略 ...
const bem = (block, element, modifier) => { /* ... */ }; // ← 这里报错!
两个同名的 const bem 出现在同一作用域中,语法上确实非法!
但问题是:Vant 的源码中每个 bem 都是在独立模块中定义的啊,为什么会被合并成一个文件并暴露为同名变量?
这时我意识到:这已经不是 Vant 的问题,而是 打包器如何处理模块作用域 的问题 —— 而 Vite 生产构建用的是 Rollup。
💡 四、真相大白:Rollup 版本升级引发的作用域 bug
运行以下命令查看当前项目中实际使用的 rollup 版本:
npm ls rollup
结果让我震惊:
my-project@0.1.0
└─┬ vite@5.4.1
└── rollup@4.50.1 # ← 新版本!
而我之前稳定的环境用的是:
rollup@4.50.0
🔍 关键点来了:
- Rollup v4 引入了更激进的“静态提升”和“作用域分析”机制;
- 在某些情况下(尤其是处理多个同名局部变量时),它会尝试复用变量名或将函数提升到外层作用域;
- 而像 Vant 这样的 UI 库,多个组件内部都定义了一个私有的
bem()函数,原本应保留在各自模块作用域内; - 但在新版本 Rollup 的优化逻辑下,这些
bem被错误地合并到了同一作用域,导致SyntaxError。
🎯 结论:
这不是语法错误,也不是代码错误,而是 Rollup v4 在特定场景下的作用域处理缺陷,影响了基于它的构建工具(如 Vite)对第三方库的正确打包。
✅ 五、解决方案
方案 1️⃣:锁定 Rollup 版本(临时推荐)
如果你使用的是 Yarn,在 package.json 中添加:
"resolutions": {
"rollup": "4.50.0"
}
然后重新安装依赖:
yarn install
如果你使用的是 pnpm,创建 .pnpmfile.cjs 文件:
function readPackage(pkg) {
if (pkg.name === 'rollup') {
pkg.version = '4.50.0';
delete pkg.dependencies;
delete pkg.optionalDependencies;
delete pkg.peerDependencies;
}
return pkg;
}
module.exports = { hooks: { readPackage } };
再运行 pnpm install。
✅ 效果:强制所有依赖共用旧版 Rollup,避免作用域合并 bug。
方案 2️⃣:排除 Vant 预构建(临时 workaround)
在 vite.config.js 中配置:
Js
深色版本
export default defineConfig({
optimizeDeps: {
exclude: ['vant']
}
})
这样 Vite 不会对 vant 做预构建,绕过 esbuild 处理环节。
⚠️ 缺点:可能影响启动速度,且治标不治本。
方案 3️⃣:等待官方修复或降级插件
关注 Rollup 官方仓库 和 Vite 仓库,看是否有相关 issue 被修复。
同时检查哪些插件引入了高版本 Rollup:
npm ls rollup
如有必要,暂时降级或替换该插件。
🛡️ 六、如何预防此类问题?
- 锁版本很重要:使用
package-lock.json/yarn.lock/pnpm-lock.yaml并提交到 Git; - 谨慎升级插件:尤其是一些小众 Vite 插件,可能引入不稳定依赖;
- CI 环境保持一致性:确保本地与线上构建环境一致;
- 监控依赖树变化:可通过
npm ls rollup等命令定期检查关键依赖版本。
📣 七、写在最后
这次问题看似只是一个简单的“变量重复声明”,实则牵扯出前端工程化中一个深层话题:构建工具链的稳定性与兼容性。
我们常常只关注业务逻辑和框架升级,却忽略了底层工具(如 Rollup、Esbuild、Babel)的版本变动也可能带来毁灭性的影响。
🌟 经验总结一句话:
当你遇到“莫名其妙”的构建报错时,别急着改代码,先看看是不是 依赖版本变了!