背景
我们希望对webpack的配置做一次封装,计划发布一个叫webpack-config的包,这个包中包含了各种配置和依赖。
以less-loader为例,webpack-config包中的依赖是这样的:
{
"name": "webpack-config",
"dependencies": {
"less-loader": "^7.0.1"
},
"peerDependencies": {
"webpack": "^4.0.0"
}
}
但这里有一个问题,less-loader有一个peer依赖less,也就是说它并不自带less的实现,而是需要使用方来安装。
在这个节点,产生了2种可能的选择:
- 让webpack-config来安装less。
- webpack-config设定less为peer依赖,让最终用户来安装less。
上面2个方案中的第2个会增加使用方的成本(要知道需要安装less,并维护less的版本)。但方案1也有可能产生另一个问题。
关键问题
less-loader依赖less的3.x版本。如果webpack-config安装了less@3.x,且npm没有hoist的能力,这个依赖结构应该是这样的:
/node_modules
/webpack-config
/node_modules
/less # 3.x
/less-loader
按照npm的逻辑,此时less-loader引用less肯定能获取正确的3.x版本,毫无问题。
但npm是有hoist能力的,即版本不冲突的包会被尽可能地移到上层,所以真实情况下目录结构是这样的:
/node_modules
/less # 3.x
/less-loader
/webpack-config
这个结构也是正确的,less-loader可以获取到less的3.x版本。
问题在于,在这时用户并不知晓webpack-config、less-loader、less之间的版本诉求关系。所以用户有可能因为自己的需要,装上一个less@2.x版本。
假设npm的hoist逻辑和我们所想的一样,没有特殊的处理,那么它会变成:
/node_modules
/less # 2.x
/less-loader
/webpack-config
/node_modules
/less # 3.x
可以看到,less-loader被hoist了,而less由于和用户的版本冲突,保留在了webpack-config里面。这个情况下,less-loader会引用到一个错误的版本,无法工作。
研究
不过我们还是认为,这个场景npm应该会考虑到。因此我们针对npm和yarn做了一下研究,去复现前面的情况。
生成一个代码库,然后使用以下命令来模拟:
npm i @private/webpack-config webpack less@2.x # 用npm
yarn add @private/webpack-config webpack less@2.x # 用yarn
当使用npm时,目录结构如下:
/node_modules
/less # 2.x
/less-loader
/webpack-config
/node_modules
/less # 3.x
使用yarn时,目录结构如下:
/node_modules
/less # 2.x
/webpack-config
/node_modules
/less # 3.x
/less-loader
值得注意的是,npm的7.x版本生成的结构与yarn一样。不过npm 7.x对peer的要求过于严格,我想很少有项目能完整通过它的要求的。
结论
不言自明。npm在6.x版本及以前并没有在进行hoist的时候考虑到peerDependencies的影响,在某些情况下会造成peer依赖的版本对不上的问题。而yarn则会有效处理这一类的问题,确保版本正确。
因此,我们的结论是:
- 如果希望供npm使用,且你只是一个npm包而不是依赖链终点的实际app,那么不要“帮用户”安装peer依赖,而是在自己的package.json中加上相同的peerDependencies,请最终用户来满足这些依赖。
- 如果仅供yarn使用,你可以放心地帮用户把peer依赖全部搞定,保持对用户封装效果,减少用户的使用成本。
- 由于这种情况出现的概率其实很小,且npm 7.x会解决这一问题,因此推荐帮用户装上peer依赖,真出了意外可以推荐用户等npm 7.x。