昨天在搞项目的微前端(Module Federation + Vite),本来一切正常,结果新拉了个分支 npm install 之后,项目直接起不来了,各种依赖解析报错,各种 shared module 缓存诡异的问题。
排查了整整大半天,最后发现是 @module-federation/vite 这个包惹的祸!今天复盘一下这个坑,希望能帮到遇到同样问题的兄弟们。 案发现场 我们平时用 npm i 安装包的时候,如果没有指定版本, package.json 里默认会加上一个 ^ (脱字符),比如:
"@module-federation/vite": "^1.14.5"
就是这个 ^ 坑了我。这个符号的意思是: 安装匹配该主版本号的最新次版本和补丁版本 。 也就是说,哪怕我锁了 "^1.14.5" ,当我或者同事在新的环境下执行 npm install ,只要官方发布了 1.15.0 ,npm 就会“贴心”地自动把 package-lock.json 里的版本更新,把包拉到 1.15.0 的最新版!
(注:如果是 ~ 符号,则只匹配补丁版本的更新,比如 ~1.14.5 最多只会升到 1.14.9 ,不会升到 1.15.0 ,相对安全一些。) 1.15.0 到底出了什么问题? 我去翻了 @module-federation/vite 的官方仓库,发现他们确实刚刚发布了 1.15.0 。 结果这个版本在处理 shared (共享依赖)的时候引入了一些致命的 Bug,导致像我们这种深度依赖 Vite 开发环境和 MF 共享机制的项目直接崩溃。
具体来说, 1.15.0 带来的问题包括:
- 子路径导入(Subpath imports)失效 :如果我们 shared 了一个依赖,但在代码里用了子路径导入(比如 import { xxx } from 'lodash/fp' ),系统就无法正确识别和共享了。
- Vite 预构建冲突 :Vite 在开发环境会用 esbuild 做依赖预构建,这个版本里 MF 的 shared 机制和 Vite 原生的依赖优化(Optimization)产生了严重的兼容性问题。
- 宿主环境独有模块缓存问题 :如果某个共享模块只有 Host 端有,Remote 端没有,缓存初始化的逻辑存在缺陷。 官方紧急抢救:1.15.1 发布 估计是昨天提 Issue 的人太多了,作者 @gioboa 紧急发布了 1.15.1 来擦屁股,修复了上述的三个大坑。 相关的 PR 包括:
- fix: respect shared subpath imports (#666)
- fix: fix up shared deps Vite optimization interop (#667)
- fix: fix up host-only shared module cache seeding (#665) 解决方案与血泪教训
第一种方案(也是我最终的选择):锁死版本 为了保证团队每个人的开发环境、以及线上 CI/CD 构建的一致性,我把 package.json 里的 ^ 去掉了,死死锁在了上一个稳定版本:
"@module-federation/vite": "1.14.5"
第二种方案:直接升到 1.15.1 如果你非常想要体验 1.15.x 带来的新特性,请一定要确保你的包版本是 >=1.15.1 ,跳过那个有毒的 1.15.0 。 总结
- 很多时候业务代码没动,但项目突然跑不起来了,大概率是底层的某个包偷偷发了包含 Break Change 或者 Bug 的 minor 更新。
- 对于 vite 、 module-federation 这种底层的构建工具链, 强烈建议把 package.json 里的版本号写死(去掉 ^ 或 ~ ) 。构建工具的稳定性大于一切!
- 定期 review package-lock.json 的 diff,不要瞎提交 lockfile。 希望这篇复盘能帮大家少掉几根头发。你们平时开发中有没有被这个 ^ 符号坑过?欢迎在评论区倒苦水!
单个项目好像很少遇到这个问题吧,之前我基本很少遇到,微前端多个项目嵌在一起是真的有很多bug,不知道乾坤会不会也是很多bug