我们知道yarn安装依赖的时候会尽量使其扁平化。所谓扁平化就是通过分析依赖树,把同一个包的多个版本解析出来,然后选择一个最大兼容的版本
提升到根目录,这个版本会在满足版本范围的情况下尽可能被复用。如果这个版本不满足一个依赖的版本范围,该依赖会把符合自己版本范围的包安装在自己的node_modules里面。
本文就是来详细的分析不同的多版本情况下,提升和去重的结果是什么样子。
1. 不同版本范围完全都不兼容时, 最低的版本会提升,其他版本安装在依赖自己的目录里
package.json
|-- A (1.0.0)
| |-- D (^1.0.0)
|
|-- B (1.0.0)
| |-- D (^2.0.0)
|
|-- C (1.0.0)
| |-- D (^3.0.0)
D(1.0.0)会被提升到根目录,B和C会在自己的node_modules里面安装属于自己的版本。B会安装D(2.0.0), C会安装D(3.0.0)。 可以看到最低的版本会提升,其他版本安装在依赖自己的目录里,没有去重效果
2. 不同版本都完全兼容时,最高的版本会被提升,所有依赖都复用该版本
|-- A (1.0.0)
| |-- D (^1.0.0)
|
|-- B (1.0.0)
| |-- D (^1.1.0)
|
|-- C (1.0.0)
| |-- D (^1.2.0)
D(1.2.0)会被提升到根目录,B和C都会复用D(1.2.0)。去重效果最大化
3. 不同版本之间分组,组内兼容。此时数量最多的组里的最高版本会被提升,
|-- A (1.0.0)
| |-- D (^1.0.0)
|
|-- B (1.0.0)
| |-- D (^2.0.0)
|
|-- C (1.0.0)
| |-- D (^2.1.0)
不同版本范围的D分为两组,一组是D(^1.0.0),另一组是D(^2.0.0)和D(^2.1.0)。由于第二组的数量是大于第一组的,所以会考虑从第二组里面选最大版本做提升和去重。其结果是D(2.1.0)被提升到根目录, B,C都会复用该版本。A会在自己node_modules里面安装D(1.0.0)版本。有部分去重效果
yarn提升和去重遵循的两大原则
虽然还有很多复杂情况没有列举出来,但是从这三种情况的结果来看,我们可以得到yarn安装依赖时遵循的两个原则
- yarn安装优先会考虑尽可能多的复用
- yarn在上一点的基础上,若全都不兼容则选最低版本提升,若全都兼容则选最高版本提升,若部分兼容则在大多数里面选最高的版本
最后简单看一个更复杂的情况
|-- A (1.0.0)
| |-- E (^1.1.0)
| |-- D(^1.0.0)
| |-- F(^1.0.0)
| |--D(^1.0.0)
|
|-- B (1.0.0)
| |-- E (^1.3.0)
| |-- D(^3.0.0)
| |-- F(^3.0.0)
| |--D(^2.0.0)
|
|-- C (1.0.0)
| |-- E (^1.2.0)
| |-- D(^2.0.0)
| |-- F(^2.0.0)
| |--D(^2.0.0)
yarn对这种复杂的依赖处理是先考虑依赖树路径的最顶层的公共包节点,譬如E是依赖树第一个出现的共同依赖,参照之前的原则把E(1.3.0)提升上去,然后把E标记为resolved。然后再按照同样的方法对D,F进行提升。
Note: 不过是优先处理D还是F, 我现在还不能确定。等以后我弄明白了,再来更新
潜在问题
yarn安装依赖的这种去重的方式,优点是减少了依赖的拷贝,减小了最终代码体积,。但是提升和去重是建立在所有的包都严格按照semver的标准来发包的。如果一个包没有做好向后兼容,那么再yarn提升最高版本到根目录的场景下,复用就很有可能出现错误。
结语
yarn安装依赖的方式在不理解的情况下,有的时候真的像开盲盒一样。希望透过这边文章,让我们更好的掌握yarn提升和去重的两个原则,能够更好的处理平时项目开发中的疑难杂症。