- 原文链接 : Yarn: A new package manager for JavaScript
- 原文作者 : SEBASTIAN MCKENZIE,CHRISTOPH POJER,JAMES KYLE
- 译文出自 : 掘金翻译计划 (翻译不易,欢迎 Star 支持)
- 译者 : 达仔
- 校对者: 根号三
在 JavaScript 社区中,工程师们互相分享成千上万的代码,帮助我们节省大量编写基础组件、类库或框架的时间。每个代码包可能都依赖于其他代码,而代码间的依赖关系则由包管理器负责维护。目前最流行的 JavaScript 包管理器是npm
客户端,在 npm
仓库中提供了多达 30 万的软件包。据统计,已有超过 500 万的工程师使用 npm
仓库,其软件包下载量达到了 50 亿次/月。
在 Facebook 中,我们多年来一直在使用 npm
客户端并取得了成功,但随着代码仓库与团队人数的增长,我们在一致性、安全性以及性能方面遇到了挑战。在尝试解决每个方面的问题后,我们最终决定着手打造一套新的客户端解决方案,以帮助我们更可靠地管理依赖。我们把这个客户端工具称为 Yarn
—— 更加快速、可靠、安全的 npm
客户端的替代品。
我们在此荣幸地宣布,我们与 Exponent、 Google 和 Tilde 进行了合作,并开源 Yarn
项目。工程师在使用 Yarn
时,依然需要访问 npm
仓库,但 Yarn
能够更快速地安装软件包和管理依赖关系,并且可以在跨机器或者无网络的安全环境中保持代码的一致性。Yarn
提高了开发效率,并解决了共享代码时面临的一些问题,使得工程师们可以专注在构建新产品以及新特性上。
JavaScript 包管理方式在 Facebook 的演变
在包管理工具出现之前,JavaScript 工程师们通常依赖的项目并不多,因此会把依赖直接存储在工程目录或上传到 CDN 上。在 Node.js 出现后不久,第一个主流的 JavaScript 包管理工具 npm
被引入进来,并很快成为了最受欢迎的包管理工具之一。从此,新的开源项目不断涌现,工程师们比起以前更加乐于分享代码了。
在 Facebook 中,我们有很多项目都要依赖 npm
仓库上的代码,比如 React。但随着内部规模的扩大,我们面临着以下挑战:在跨平台与跨用户之间安装依赖时的代码一致性问题、在安装依赖时花费太长时间、以及 npm
客户端自动执行某些依赖库的代码所导致的安全性问题。我们尝试过寻找这些问题的解决方案,但在这个过程中通常又会引起一些新的问题。
尝试修改 npm 客户端
在开始阶段,我们遵循了最佳实践,在代码仓库中只跟踪了 package.json
文件的变化,并要求工程师手动运行 npm install
命令安装依赖。这种模式在开发人员的电脑上没有问题,但在持续集成环境中遇到了困难,因为出于安全与可靠性的考虑,持续集成环境需要进行沙箱隔离,不能进行联网,因此也无法安装依赖。
接下来,我们尝试在代码仓库中跟踪整个 node_modules
目录的文件变化。虽然这种方式有效,却使得一些简单操作变得复杂化了。比如,对 babel更新一个次要版本号时,会产生多达 800,000 行的提交记录,此外由于 lint 规则的存在,引起无效的 utf-8 字节序列、windows 换行符、非 png 压缩图片等问题时,将会导致工程师经常需要花费一整天的时间合并 node_modules
目录的文件。而我们负责源码控制的团队也指出,跟踪 node_modules
目录会引入过多的元数据。比如 React Native 的 package.json
文件目前只列出了68项依赖,但在运行 npm install
后,node_modules
目录整整包含了 121,358 个文件。
最后,为了有效组织 Facebook 逐渐增长的工程师人数以及管理需要安装的代码量,我们尝试修改 npm
客户端。我们决定压缩整个 node_modules
目录,并上传到内部 CDN,然后我们的工程师与持续集成系统都能从 CDN 上下载并解压文件,从而保证了代码一致性。这样我们就可以从源码控制系统中删除数以万计的文件了,但不足之处是工程师现在不仅在拉代码时需要联网了,构建也同样需要联网。
我们还试图为 npm
的 shrinkwrap功能寻求优化方案,这个工具是用来锁定依赖版本号的。但 Shrinkwrap
功能的文件默认不会生成,如果开发者忘记了生成这一步骤,文件就不会被同步更新,因此我们编写了一个工具,以确定 Shrinkwrap
的文件内容和 node_modules
目录中的文件相符。这些文件由大量的 JSON 块组成,并且键名是无序的,因此每次更改通常会导致 Shrinkwrap
文件的内容大幅变化,难以进行代码审查。为减缓这一问题,我们还需要借助一个额外的脚本,对所有条目进行排序。
最后,通过 npm
升级单个依赖包时,基于 语义化版本号规则,npm
通常会连同其他无关依赖一起更新。这使得每次更新都会比预期产生更多的变化,工程师们认为这样把 node_modules
提交上传到 CDN 的过程,难以达到预期的效果。
构建新客户端
与其围绕 npm
客户端继续构建基础设施,不如从整体上再次回顾这些问题。伦敦办公室的 Sebastian McKenzie 提出,如果我们建立一个新客户端工具以代替 npm
客户端,从而解决我们的核心问题呢?这一构思很快得到了我们的认同,团队对于这个主意也感到非常兴奋。
在开发过程中,我们与业界的工程师们进行了交流讨论,发现他们也面临着类似的问题,也尝试过许多类似的解决方案,通常只能把这些问题逐一解决。很明显,有必要把整个 JavaScript 社区正在面临的问题集合起来,然后我们就可以开发一个主流的解决方案了。在此感谢 Exponent、 Google 与 Tilde 的工程师们的协助,我们共同建立了 Yarn
客户端,并在每一个主流 JS 框架以及 Facebook 外的使用场景中测试验证了 Yarn 的性能。今天(2016-10-11),我们很荣幸把这个工具开源分享到社区中。
介绍 Yarn
Yarn
是一个新的包管理器,用于替代现有的 npm
客户端或者其他兼容 npm
仓库的包管理工具。Yarn
保留了现有工作流的特性,优点是更快、更安全、更可靠。
任何包管理器的主要功能都是安装某些软件包,软件包即用于特定功能的某段代码,通常是从一个全局的仓库安装到工程师的本地环境。每个软件包可以依赖于其他包,也可以不依赖。一个典型的项目结构的依赖树通常会包含数十个、数百个甚至上千个软件包。
这些依赖包通常是带版本号的,通过语义化版本控制(semver)安装。Semver 定义的版本号反映了每个新版本更改的类型,到底是进行了不兼容的API改动(MAJOR),还是添加了向后兼容的新特性(MINOR),还是进行了向后兼容的 bug 修复(PATCH)。然而,semver 依赖于软件包的开发者不能犯错误——如果依赖关系没有加锁,可能会引入一些破坏性更改或者产生新的 bug。
结构
在 Node 生态系统中,依赖通常安装在项目的 node_modules
文件夹中。然而,这个文件的结构和实际依赖树可能有所区别,因为重复的依赖可以合并到一起。npm
客户端把依赖安装到 node_modules
目录的过程具有不确定性。这意味着当依赖的安装顺序不同时,node_modules
目录的结构可能会发生变化。这种差异可能会导致类似“我的机子上可以运行,别的机子不行”的情况,并且通常要花费大量时间定位与解决。
Yarn
通过 lockfiles 文件以及一个确定性的、可靠的安装算法,解决了版本问题和 npm
的不确定性问题。Lockfile 文件把安装的软件包版本锁定在某个特定版本,并保证 node_modules
目录在所有机器上的安装结果都是相同的。Lockfile 还使用简洁的有序键名的格式,保证了每次的文件变化最小化,进行代码审查也更为简单。
安装过程分为以下三个步骤:
- 处理:
Yarn
通过向代码仓库发送请求,并递归查找每个依赖项,从而解决依赖关系。 - 抓取:接下来,
Yarn
会查找全局的缓存目录,检查所需的软件包是否已被下载。如果没有,Yarn 会抓取对应的压缩包,并放置在全局的缓存目录中,因此Yarn
支持离线安装,同一个安装包不需要下载多次。依赖也可以通过 tarball 的压缩形式放置在源码控制系统中,以支持完整的离线安装。 - 生成:最后,
Yarn
从全局缓存中把需要用到的所有文件复制到本地的node_modules
目录中。
通过清晰地细分这些步骤,以及确定性的算法支持,使得 Yarn
支持并行操作,从而最大化地利用资源,并加速安装进程。在一些 Facebook 的项目上,Yarn
甚至可以把安装过程降低一个数量级,从几分钟到只需几秒钟。Yarn
还使用了互斥锁,以确保多个 CLI 实例同时运行时不会互相冲突与影响。
纵观整个过程,Yarn
对于软件包安装加上了严格的限制。你可以对哪个生命周期脚本作用于哪个软件包进行控制。软件包的 checksum 也会存储在 lockfile 中,以确保每一次安装都可以得到同一个包。
特性
Yarn
除了让安装过程变得更快与更可靠,还添加了一些额外的特性,从而进一步简化依赖管理的工作流。
- 同时兼容
npm
与 bower工作流,并支持两种软件仓库混合使用 - 可以限制已安装模块的协议,并提供方法输出协议信息
- 提供一套稳定的公有 JS API,用于记录构建工具的输出信息
- 可读、最小化、美观的 CLI 输出信息
Yarn 用于生产环境
我们已经在 Facebook 中把 Yarn
用于生产环境,并且效果非常理想。Yarn
有效地管理了许多 JavaScript 项目的包依赖关系。在每次迁移时,构建都可以离线进行,因此加速了工作流程。我们基于 React Native 在不同条件下进行安装时间测试,比较了 Yarn
与 npm
的性能,具体参见这里。
起步
最简单的起步方法是:
npm install -g yarnpkg
yarn
yarn
CLI 代替了原有开发工作流中 npm
CLI 的作用,用法可能是单纯的替代,也可能是一个新的、相似的命令:
npm install
→yarn
不需要带参数,
yarn
命令会读取package.json
文件,然后从 npm 仓库中抓取软件包,并放置到node_modules
目录中。等价于运行npm install
。npm install --save <name>
→yarn add <name>
我们避免了
npm install <name>
命令中安装“不可见的依赖”的行为,并分离出一个新命令。运行yarn add <name>
等价于运行npm install --save <name>
。
未来
目前已经有许多成员一起参与到 Yarn
的构建中,以解决我们的共同问题,我们也希望 Yarn
未来能真正成为一个大众化的社区项目。Yarn
目前已经 在 GitHub 开源,我们也已经准备好向 Node 社区进行推广:使用 Yarn
、分享构思、编写文档、互相支持,并帮助构建一个很棒的社区来进行长期维护。我们相信 Yarn
已经拥有一个良好的开局,如果有你的帮助,Yarn
的未来将会更加美好。