方案背景
问题1
在升级 swr 的过程中发现了主应用对“幽灵依赖”的使用,这是主应用的代码,引用了 swr 但是我们并没有在 pkg 对其进行声明
竟然主应用 pkg文件里并没有对 swr 进行声明,swr 从何而来?幽灵依赖产生于 npm 扁平化结构;以下是我安装了@nexus/scripts的 node_module 文件列表,这意味着我明明在 pkg 文件里声明了一个@nexus/scripts却可以在主应用中使用到swr、axios、react等,这些就是幽灵依赖
我觉得这是一个潜在的bug,比如你引用了一个devDependence的子依赖,在本地开发可能没有任何问题,但是到了线上问题就出现了,线上环境不会安装devDependence,也就不会有它的子依赖。
问题2
npm 下相同的包重复安装,也就是“依赖分身”问题,比如我的主用声明了一个swr@1.0.0及@nexus/hooks@1.0.0,其中@nexus/hooks@1.0.0引用了swr@1.0.0
有两个swr@1.0.0?合理来说我只需要一个swr@1.0.0 在根的 node_module 就行了不是吗?
优势分析
不再有幽灵依赖
pnpm 创建的是一个非扁平化的node_module 项目结构我安装了 @nexus/hooks 那么我的根下也就只有它,没有其他乱七八糟的东西,清晰明了
通过软链的形式实现对分身的复用
速度优势
当使用 npm 或 Yarn 时,如果你有 100 个项目,并且所有项目都有一个相同的依赖包,那么, 你在硬盘上就需要保存 100 份该相同依赖包的副本。然而,如果是使用 pnpm,依赖包将被存放在一个统一的位置,因此:
- 如果你对同一依赖包需要使用不同的版本,则仅有版本之间不同的文件会被存储起来。例如,如果某个依赖包包含 100 个文件,其发布了一个新版本,并且新版本中只有一个文件有修改,则
pnpm update只需要添加一个新文件到存储中,而不会因为一个文件的修改而保存依赖包的所有文件。 - 所有文件都保存在硬盘上的统一的位置。当安装软件包时,其包含的所有文件都会硬链接自此位置,而不会占用额外的硬盘空间。这让你可以在项目之间方便地共享相同版本的依赖包。
最终结果就是以项目和依赖包的比例来看,你节省了大量的硬盘空间, 并且安装速度也大大提高了!
具体实践
首先有一点我要说明,大胆实践,基本没有额外的学习成本,pnpm 的使用方式与 npm 的使用方式基本相同,低成本好东西为什么不用?
安装 pnpm
npm i pnpm -g
把过去使用的“幽灵依赖”进行显示声明
pnpm install后启动项目根据报错对“幽灵依赖”进行显示声明:
执行命令
pnpm import根据 yarn.lock 或者 package.lock 生成 pnpm.lockpnpm install开始安装依赖
其他问题
基本根据上述的步骤不会出现问题;如果还有问题,可以配置.npmrc 将这些包提升至根node_module就可以了
比如我们项目中一些包引用了 react16,而根是 react17,出现这样的报错
.npmrc 配置
# root/.npmrc
# 把相关库提升到公共(项目)node_modules下
public-hoist-pattern[]=*ahooks*
public-hoist-pattern[]=*@ant-design*
# 使用扁平方式,除非实在定位不到问题,不建议使用,这样就又回到 npm、yarn 的使用了
# shamefully-hoist=true