原文地址:Yarn: A new package manager for JavaScript

在JavaScript社区,工程师共享成百上千的代码片段来避免重写基本的组件、库以及框架。如此的话,每个代码片段都有可能依赖其他的代码片段。而包管理器就是用来管理依赖的。当前最流行的JavaScript包管理器是npm。其在npm registry下管理着超过300,000的js包,超过500,000工程师使用npm registry,每个月有超过5000,000,000的下载量。

Facebook已经成功使用npm几年了,但是随着代码量和工程师变得越来越多,我们碰到了不少问题,包括一致性、安全性以及性能等。解决这些问题带给我们经验让我们有能力构造了一种新的更加可靠的管理包的解决方案。该解决方案就是Yarn —— 一种快速、可靠、安全的npm替代品。

现在我们正式开源Yarn,Yarn是Facebook、Exponent、Google以及Tilde合作的产物。使用yarn,工程师依然能够访问npm registry,但是其能够更快的安装包,在机器之间或者安全的离线环境下管理包并保证一致性。Yarn使得工程师能够更加聚焦,只要关心他们自己的产品和特性。

Facebook内JavaScript包管理器的演进

在包管理器出现之前,js工程师通常指依赖一小部分依赖,他们通常是被直接放在工程里面或者通过CDN获取。第一个包管理器在Node.js出现之后不久就出现了,很快,它就变成了世界上最流行的包管理器。成百上千的开源工程被创建出来并共享,工程师更加愿意共享代码和使用共享代码了。

Facebook的很多项目,比如React,依赖npm registry上的代码。然而随着我们的快速迭代,在不同的机器和用户安装依赖的时候出现了一致性、耗时以及安全问题。我们尝试新的解决方案来解决这些问题,但是经常又会出现新的问题。

基于npm的尝试

开始阶段,我们遵循最佳实践,检查package.json然后让工程师手动执行npm install。这个方法对于工程师是有效的,但持续集成系统却挂掉了,持续集成需要保证沙盒环境而且却要和内部的环境隔离以保证安全和可靠性。

接下去的解决方案把所有的node_modules也提交到代码库。虽然该方案能够work,但是其让一些简单的操作变得很麻烦。例如更新一个babel的小版本会产生大概800,000行的代码提交,其导致lint没法正常工作。将node_modules的变更合进去会花费工程师一整天的时间。而且这种模式会导致巨量的metadata。React Native的package.json包含68个依赖,执行npm install之后就会多打121,358文件。

最后我们还尝试一种方法,我们将我们需要依赖的代码打成一个zip包,然后将他上传到CDN,这样的话,不论是我们的工程师还是持续集成系统都能正常工作。这使得我们能够从我们的源码控制中移掉成百上千的代码。但是这样的话,不论是拉新代码还是构建都需要连接网络。

同样我们还必须要解决shrinkwrap的问题,因为我们需要使用它来锁定依赖版本。shrinkwrap文件默认是不会生成的,所以如果工程师忘记生成srinkwrap文件,那其不能保证一致性了。所以我们写了一个工具来保证node_modules中的文件和是srinkwrap一样的。这个文件是巨大的带有未排序key的JSON块,因此更改他们会产生巨大的不能理解的提交。所以我们又需要一个用来排序这些条目的工具。

因此更新单个依赖导致npm也更新了很多不相关的依赖。这导致每次变更都要比预想的更大,而且还要附带一系列的其他工作导致其很繁琐。

构建一个新的包管理器

我们决定不再围绕npm来构建基础设施,我们打算将这个问题看成一个整体。Sebastian McKenzie开始了部分hack工作,并取得了令人激动的效果。

随着我们工作的深入,我们发现公司内部很多工程师都遇到相似的问题。如果我们能够收集整个社区遇到的问题,我们能够提供一种解决方案,也解决几乎所有人的问题。通过和Exponent、Google以及Tilde工程师的通力合作,我们造出了Yarn,并在主流的js框架上做了性能测试。今天我们非常激动地和社区分享Yarn。

Yarn

Yarn是一个新的包管理器,其用来替换已经非常流行的npm或者其他和npm兼容的包管理器。其和npm有一样的功能,但更快速、更安全、更可靠。

任何包管理器的主要功能都是从全局的registry安装包(具备特定功能的代码片段)工程师的本地环境。每个包可能依赖也可能不依赖其他的包。一个典型的工程可能有几十、几百甚至上千个依赖。

这个依赖是有版本号的,通过semver来决定是否安装。Semver通过版本来反映变化的类型:API变化、新特性增加还是bugfix。然后semver依赖包开发者不做错事。

架构

在Node生态中,依赖被存放在node_modules中。然后文件结构可能和真实的依赖树不太一样,因为通常会有重复的依赖,而他们会被merge到一块。npm安装依赖到node_modules是不确定的,其主要是安装依赖的顺序决定的,所以如果依赖的顺序不一样,不同的人npm install之后的结果很有可能是不一样的。这个不同可能导致在我的机器上能跑,但别处挂掉的问题。

Yarn通过使用lockfiles来解决版本和不确定的问题,安装算法是确定的,也是可靠的。lockfile锁定安装的文件为特定的版本,保证他们安装的时候node_modules的文件结构是一致的。lockfile使用带有排好序的key的简洁的格式来保证最小的变化,是的review变得简单。

安装的过程被拆分成三个主要的步骤:

  1. 解析:Yarn通过请求registry来解析依赖,然后递归的查看依赖
  2. 获取:然后Yarn在一个全局的cache目录中查看包是否已经下载。如果没有,Yarn获取包,将其放到全局cache中,因此其可以离线工作,而且不需要下载多次。依赖也可以被放在代码库中,这样可以支持完成的离线安装。
  3. 链接:最后Yarn将所有东西链在一起,其将需要的文件从全局的依赖中拷贝到node_modules目录。

通过将安装步骤拆分,我们有了确定的结果,Yarn能够并行操作,最大化资源的利用,使得安装过程更加快速。在一些Facebook的工程里面,从几分钟减到几秒钟。Yarn还使用了一个互斥锁来保证多个在跑的CLI示例不会互相污染。

在整个过程中,Yarn在包的安装过程中加了严格的保证。你能控制在什么生命周期对哪些包做什么事情。包的checksum也在lockfile中,保证你每次安装都是正确的。

特性

除了让安装更快速、更可靠之外,Yarn还有些别的特性来简化依赖管理

  • 对npm和bower工作流都是支持的,而且支持混合registries
  • 能够严格控制安装模块的许可
  • 暴露稳定logging相关的JS API
  • 可读,最小化,漂亮的CLI输出

Getting started

npm install -g yarn
yarn

yarnnpm的命令式类似的

  • npm install -> yarn
  • npm install --save <name> -> yarn add <name>