w3ctech

Facebook推出新的JavaScript模块管理器:Yarn

在JavaScript社区,为了避免我们重写一些基础的组件,库,或者是框架,工程师们分享了成千上万的代码片段。这些代码反过来也许又依赖了其他代码片段,这些依赖都由模块管理器来进行管理。当下最流行的JavaScript模块管理器就是npm,在npm代理中提供了超过300,000个模块。有超过五百万的工程师使用npm,每个月多达五十亿的下载量。

多年来,我们在Facebook都使用着npm,然而随着我们代码库大小和工程师数量的增长,我们在一致性,安全性,以及性能上都遇到了一些问题。在每出现一个问题我们就试图去解决它的情况下,我们开始建立一个新的解决方案来帮助我们更可靠的管理依赖。这项工作所诞生的产品就是Yarn — 一个更快,更可靠,更安全的可选择的npm。

我们很高兴可以和Exponent,Google,Tilde合作,发布了Yarn的开源版本。使用Yarn,工程师依旧可以访问npm的代理,不过可以更快安装模块,更好的管理跨机器的依赖关系,以及保障离线环境下的安全问题。当共享代码时,Yarn为工程师提供了更快的速度,使其拥有更多的时间关注于创建新的产品或是特性。

Facebook的模块管理器进化史

在没有模块管理器的时候,多数的JavaScript工程师只能通过直接在项目中保存或是CDN服务来使用少量的依赖。第一个主要的JavaScript模块管理器,npm,在Node.js之后不久被推出,并且很快的成为了世界上流行的模块管理器之一。成千上万的开源项目被创建,工程师们共享了更多的代码。

我们Facebook的许多项目,例如React,也依赖了npm中的代码。然而就像我们所经历的,我们面临了在不同机器和用户上安装依赖不一致,花费大量时间来引入依赖,以及npm自动执行依赖中代码所带来的一些安全隐患等问题。我们尝试用某些方式来解决这些问题,但是经常会因此带来新的问题。

尝试扩展npm

最初,遵循规定的最佳实现,我们只是检查了package.json然后要求工程师手动执行npm install。工程师使用这个方法很好的奏效了,但是它在我们不断整合的环境中却崩溃了,为了安全性和可依赖行,它需要沙盒式封装并且切断网络。

我们应用的下一个解决方案就是检查库中所有的node_module。当这个方法生效的时候,它使得一些简单的操作变得复杂了。例如,更新少数babel的版本时会产生800,000行的提交,这使得登录和触发校验非法utf8字节序列,windows换行符等等的规则变得困难了起来。合并node_modules中的不同通常要花费工程师们一整天的时间。我们的源代码控制团队还指出,我们检查node_module就要接管大量的元数据。

由于Facebook工程师和代码数量的问题,我们做了最后一次尝试来扩展npm。我们决定压缩整个node_module文件夹,然后把它上传到内部CDN上,以便于工程师和我们连续整合的系统都可以下载和提取一致的文件。这使我们得以删除了源码中成千上万的文件,却导致了工程师不仅需要网络连接去拉取新代码,还要去构建项目。

并且我们的工作不得不总是被npm的shrinkwrap特性所带来的问题包围着,我们用它来锁定依赖版本。Shrinkwrap文件不是默认生成的,一旦工程师忘记生成它就无法进行同步,因此我们写了一个工具来验证shrinkwrap中的内容与node_module是匹配的。文件中是巨大的JSON和无序的key,因此,改变他们会生成大量的,难以review的提交。为了减轻这个负担,我们需要添加一个额外的脚本来对所有的条目进行排序。

最后一点,基于语义版本控制规则,使用npm更新一个单独的依赖就会同时更新许多无关的文件。这导致每次的改变都比预期大很多,还不得不去做一些类似提交node_module或是上传它到CDN这样在工程师看来认为与理想的过程不符合的行为。

搭建新的客户端

与其不停的解决npm的基础问题,我们决定尝试着整体的来看待问题。那么如果试着去搭建一个新的客户端来处理我们所遇到的核心问题怎么样呢?Sebastian McKenzie在我们的伦敦工作室中开始hack这个思想,很快,我们因为它的可行性而开始兴奋起来。

我们着手去做这件事,开始和一些行业中的工程师对话,发现他们也都面临着相似的问题并且也尝试了很多相同的解决方案,每次都是去解决当下所遇到的问题。因此很显然,我们可以合作开发一套方案,来解决我们共同遇到的问题。在来自Exponent,Google和Tilde的工程师帮助下,我们搭建了Yarn并且测试和验证了它在每个主流JS框架下以及在Facebook之外的使用场景的表现。

关于Yarn

Yarn是一个新的模块管理器,用来代替npm以及其他模块管理器现有的工作流程,并且保持了对npm代理的兼容性。它在与现有工作流程功能相同的情况下,保证了操作更快,更安全,更可靠。

任何模块管理器的主要功能都是去安装一些模块 — 一段服务于特定目的的代码 — 从一个全球的代理到工程师本地的环境。每个模块可能依赖其他的模块,也可能不依赖。一个典型的项目会拥有大概有数十,上百,甚至是上千个模块的一颗依赖树。

这些依赖都是版本化并且基于语义版本控制(semver)安装的。Semver对于版本控制方案的定义是要反映每个新版本改变的类型,是否更换了API,添加了新特性,或者是修改了一个bug。然而,Semver要依赖于模块开发者没有犯错误 — 在依赖没有被锁定的情况下,新的漏洞或者新的bug也许会按照某种方式出现在安装的依赖中。

体系结构

按照节点系统,依赖都放置于你项目中node_module的目录下。然而这个文件结构可能会与实际依赖树不用,因为重复的依赖关系是被合并到一起的。npm将依赖随机的安装进node_module目录中。这意味着,基于人们依赖安装顺序,node_module目录的结构是不同的。这种不同会导致“在我机器中出现”的bug需要花费很长时间来寻找。

Yarn通过锁定文档以及一个确定并且可靠的安装算法解决了关于版本控制和非确定性的这些问题。这些锁定文档锁定了安装的依赖在特定的版本,并且保证每次安装结果在所有机器中生成的都是完全相同的node_module文档结构。锁定文档使用了包含有序key的简介格式,以确保可以进行微小的改动和简单的review。

安装过程分解为三个步骤:

  1. 分解:Yarn通过提交请求和递归的查找每个依赖来分解依赖关系。
  2. 抓取:接下来,Yarn查找全局缓存目录来确认是否有需要的模块已经下载过了。如果没有,Yarn会抓取模块的压缩包并且在放置在全局缓存中以保证离线情况可以工作并且不需要再一次下载依赖。依赖同样可以以压缩包的方式放置在源码中以方便离线安装。
  3. 链接:最后,Yarn通过从全局缓存中复制所需的文件到本地的node_module目录下来进行链接。

通过分解成这些步骤可以干净的产生确定的结果,Yarn可以进行并行操作,最大限度的提高资源利用并且使安装过程加快。在一些Facebook的项目上,Yarn将安装过程时间减少了一个数量级,从几分钟到仅仅数秒。Yarn同样应用互斥来确保多个运行的命令行实例不会碰撞和互相污染。

在整个过程中,Yarn对于模块的安装有严格的保证。你可以控制哪个生命周期脚本执行哪个模块。模块的校验也存储在锁定文件中以保证每次都得到相同的模块。

特性

为了使安装更快更可靠,Yarn有一些额外的特性,以进一步简化依赖管理的工作流程。

  • 对于npm和bower工作流及支持混合注册的兼容性。
  • 能够限制已安装的模块的许可证和输出许可信息的手段。
  • 公开了一个稳定的公共JS API,通过构建工具提供了抽象日志记录。
  • 可读,最小的,干净的命令行输出

在生产环境中使用 Yarn

我们在 Facebook 的生产环境中已经使用 Yarn 了,在许多的 JS 项目中它帮助管理我们的模块和依赖,并且一切都正常。每次迁移部署工程师都能够离线安装提高他们的工作效率。这里 列出了不同条件下 Yarn 和 npm 安装 React Native 的耗费时间,可以看出 Yarn 的确比 npm 快很多。

开始使用

想要使用 Yarn 只要执行如下命令:

npm install -g yarn

yarn

yar 命令对大部分开发过程中需要用的 npm 命令都有对应或类似的版本,例如:

  • npm installyarn

    无参情况下 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>,保证依赖永远会被记录在 package.json 中。

未来

为了解决常见的问题,我们好多人聚集到一起搭建Yarn,并且真切的希望 Yarn 能成为一个开源项目提供给每个人使用。Yarn 现在已经在 Github 上开源。大家一起使用 Yarn,分享创意,编写文档,创建良好的社区氛围,让它成为 Node 社区最棒的模块管理工具。我们相信 Yarn 已经准备好为大家提供服务,并在大家的帮助下变得更好!

w3ctech微信

扫码关注w3ctech微信公众号

共收到1条回复