0%

Yarn的安装理念

当npm还处在v3阶段时,一个叫做Yarn的包管理方案横空出世。2016年,npm 还没有 package-lock.json 文件,安装速度很慢,稳定性也较差,而 Yarn 的理念很好地解决了以下问题。

  • 确定性:通过yarn.lock等机制,保证了确定性。即不管安装顺序如何,相同的以来关系在任何机器和环境下,都可以以相同的方式被安装(在npm5之前,没有package-lock.json机制,只有默认并不会使用的npm=shrinkwrap.json)
  • 采用模块扁平安装模式:将依赖包的不同版本,按照一定策略,归结为单个版本,以避免创建多个副本造成冗余
  • 网络性能更好:Yarn采用了请求排队的理念,类似并发连接池,能够更好地利用网络资源;同时引入了更好的安装失败时的重试机制
  • 采用缓存机制,实现了离线模式(npm目前也有类似实现)

该结构整体和package-lock.json结构类似,只不过yarn.lock并没有使用JSON格式,而是采用了一种自定义的标记格式,新的格式仍然保持了较高的可读性。

相比npm,yarn另外一个显著区别是yarn.lock中自以来的版本号不是固定版本。这就说明单独一个yarn.lock确定不了node_modules目录结构,还需要和package.json文件进行配合

Yarn 安装机制和背后思想

简单来说,Yarn 的安装过程主要有以下 5 大步骤:
检测(checking) => 解析包(resolving packges) => 获取包(fetching packages) => 链接包(linking packages) => 构建包(building packages)

检测包

这一步主要是检测项目中是否存在一些npm相关文件,比如package-lock.json等。如果有,会提示用户注意:这些文件的存在可能会导致冲突。

解析包

这一步会解析依赖树中每一个包的版本信息
首先获取当前项目中package.json定义的dependencies、devDependencies、optionalDependencies 的内容,这属于首层依赖
接着采用遍历首层以来的方式获取依赖包的版本信息,以及递归查找每个依赖下潜逃的版本信息,并将解析过和正在解析的包用一个Set数据结构来存储,这样就能保证同一个保本范围内的包不会被重复解析。
• 对于没有解析过的包A,首次尝试从yarn.lock中获取到版本信息,并标记为已解析
• 如果在Yarn.lock中没有找到包A,则向Registry发起请求获取满足版本范围的已知最高版本的包信息,获取后将当前包标记为一解析。

获取包

这一步我们首先需要检查缓存中是否存在当前的依赖包,同时将混存中不存在的依赖包下载到缓存目录。

链接包

上一步是将依赖下载到缓存目录,这一步是将项目中的依赖复制到项目node_modules下,同时遵循扁平化原则。在复制依赖前,yarn会先解析peerDependencies,如果找不到符合peerDependencies 的包,则进行warning提示,并最终拷贝依赖到项目中。

构建包

如果依赖包中存在二进制包需要进行编译,会在这一步进行

破解依赖管理困境

早起npm的设计非常简单,在安装依赖时将依赖放到项目的node_modules文件中;同时如果某个直接依赖A还依赖其他模块B,作为间接依赖,模块B将会被下载到A的node_modules文件夹中, 依此递归执行,最终形成了一颗巨大的依赖模块树。
这样的 node_modules 结构,的确简单明了、符合预期,但对大型项目在某些方面却不友好,比如可能有很多重复的依赖包,而且会形成“嵌套地狱”。
那么如何理解“嵌套地狱”呢?

• 项目依赖树的层级非常深,不利于调试和排查问题
• 依赖树的不同分支里,可能存在同样版本的相同依赖。比如直接依赖A和B,但A和B都依赖相同版本的模块C,那么C会重复出现在A和B的node_modules中

这种重复问题使得安装结果浪费了较大的空间资源,也使得安装过程过慢,甚至会因为目录级层太深导致路径太长,最终在windows系统下删除node_modules文件夹出现失败情况。

因此,npm v3版本之后,nodu_modules的结构改成了扁平结构