yarn安装对于同一个包的多个版本是如何提升和去重的?

648 阅读3分钟

我们知道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提升和去重的两个原则,能够更好的处理平时项目开发中的疑难杂症。