目标
同一个库的两个版本同时进包,意味着双倍字节、双倍解析时间,有时还有两个独立模块注册表把单例状态撕成两半。这种重复在代码评审里完全不可见,因为它是解析器制造的,不是你写的哪行 import 造成的。本食谱把重复版本列出来、按导入方归因、在解析层合并,再用 CI 门禁让这类回归无法静默回归。
步骤要点
- 用 pnpm why 或 npm ls 列出多版本并存的包及其依赖路径,确认嵌套的旧版本是被哪个库拉进来的。
- 跑构建并出分析报告,量化每个副本的 gzip 体积,把抽象的“重复”翻译成已交付的字节数。
- grep 源码里的 import 并结合 why 输出做归因,找出拉进第二份副本的确切导入链。
- 在解析层合并:pnpm 用 overrides(可按父包窄化作用域),vite/webpack 用 alias 加 dedupe 列表,重装后重构建。
- 加 CI 重复检查脚本遍历整棵依赖树,发现同名不同版本就按包名报错,锁文件一变即评估。
排错提示
override 弄坏旧消费方说明嵌套版本是为 API 差异而存在的,先升级消费方,或收窄 override 作用域并显式测试旧组件再合入;npm ls 干净但产物里有重复是混用模块格式(lodash 与 lodash-es 双入场),用 alias 统一或两都进 dedupe;不升大版本就消不掉重复时先量化 KB,12KB 的重复是工单不是事故,CI 检查会防止它复利增长。
适用边界
解析层去重会改变实际执行的代码,嵌套版本可能事出有因,合并前必须测试受影响导入方;重复检查只抓版本重复不抓普遍膨胀,要与体积预算并存而非替代。
代码示例
# Which packages exist twice, and why?
pnpm why lodash
# old-table-component 1.2.0
# ├─ lodash 3.10.1 <-- nested old copy
# charts-widget 4.1.0
# └─ lodash 4.17.21
# What does that cost in the artifact?
# bundle-report: lodash@3 21.4KB gzip (dead weight)
# lodash@4 24.9KB gzip (the real one)
# Collapse it
pnpm.overrides: { "old-table-component>lodash": "^4.17.21" }
# bundle after: 46.3KB -> 24.9KB gzip for lodash total