在 JavaScript 社区,工程师们分享成千上万个代码包,使得我们不用重复编写我们自己的基本组件,库,或者框架。每一个代码库又可能依赖于另外的几个代码库,这些依赖被包管理器所管理。最出名的 JavaScript 包管理器是 npm client, 它提供了连接进 npm 仓库(registry) 的方式,它拥有超过30万个包! 还有超过 500 万工程师使用着每月下载量超过 50 亿的 npm client!
Facebook 曾很开心地用着 npm client 很多年,但是随着我们基础代码的扩大和工程师的加入成长,我们碰到了 一致性(consistency),安全性(security)和性能(performance) 的问题。在每次出现问题之后我们都竭力尝试去解决,之后,我们开始构建一个新的解决方案去帮助我们更可靠地管理我们的依赖包。这个产品被称为 Yarn —— 一个快速的,可靠地,安全的 npm client 替代品。
我们很高兴地宣布这个 Exponent, Google, Tilde 的协作产品 —— Yarn 的开源。有了 Yarn,工程师仍然可以进入 npm 仓库(registry),但是安装 包(package) 会更快,并且依赖管理在跨机器或者安全离线环境下也拥有一致性。Yarn 使得工程师们可以更快地,更有信心地使用别人的代码,这样他们就能关注于他们的工作 —— 创建一个新产品,创造一个新特性。
JavaScript 包管理器在 Facebook 的进化
在包管理器出现之前,JavaScript 工程师们把几个数量很少的依赖,直接存在项目,或者放在 CDN 中是很常见的。第一个主流的 JavaScript 包管理器,npm, 在 Node.js 被发布(introduced) 很短时间之后就被做出来了,它也很快变成了世界上最受欢迎的包管理器之一。成千上万的新的开源项目被创建,工程师们也比原先分享了更多的代码。
我们 Facebook 中,像是 React 的很多项目都依赖 npm 的代码。然而随着我们的内部拓展,我们遇到了一些一致性的问题:在不同的用户和机器之间安装依赖的时候,它总是会花很多时间下载依赖,还有会自动从其他依赖中运行一些代码这样的安全问题。我们尝试去围绕着这几个问题做出解决方案,但是它们又会引发出新的问题。
尝试去拓展 npm 客户端
最初,跟着最佳实践,我们只是检查 package.json 并要求工程师们手动执行 npm install
. 这些工作对工程师来说足够了,但是在我们的 CI 环境中就挂了。CI 环境因为安全性和可靠性的原因需要进沙盒并且断网。
我们实施的下一个方案是把 node_modules 里面的东西放到仓库里。当做的时候发现这些简单的操作其实非常困难。比如,为 babel 更新了一个小版本就产生了 80 万行的提交。这些提交很难去 land 并触发针对非法 uf8 字节序列的 lint 规则的,非法 utf8 字节序列就像是 windows 结束符,非压缩的 png 图片还有其他。。。合并这些改变到 node_modules 通常会使得几个工程师花费上一整天。我们的代码管理团队也指出我们检测 node_modules 文件夹必须得为数量巨大的元数据负责。React Native 的 package.json现在只是列了 68 个依赖,但是运行 npm install 之后,node_modules 文件夹中会包含 121,358 个文件。
我们做出的最后一个尝试是拓展 npm 客户端,使之适应我们 Facebook 的工程师们和需要安装的代码量。我们决定去压缩整个 node_modules 文件夹并把它上传到一个内部 CDN,这样我们的工程师和 CI 系统都能有着一致性下载并解压这些文件。这样使我们在代码管理中移除了成百上千个文件,但是这么做的话,我们的工程师联网不仅仅是去下载新代码,还要创建它。
我们也不得不围绕着我们曾用来锁定依赖版本号的 shrinkwrap 特性搞事。Shrinkwrap 文件不会默认生成,并且如果工程师忘记的话,它也会在同步的时候丢失。所以我们就写了个工具去验证 shrinkwrap 文件是否匹配 node_modules 中的内容。这些文件是有着未排序的 key 的数量巨大的 JSON blobs。所以对他们的改变会产生大量的,难以检查的提交。为了减轻这种(痛苦), 我们又需要去添加一个额外的脚本去对所有这些条目进行排序。
最终,更新一个 npm 依赖,也就更新了许多无关的所谓 语义更新。这使得每一个改变都比预期想象得要大。并且不得不做一些像是提交 node_modules 或者更新到 CDN 这种对工程师来说流程比预想的要少的工作。
(这一节翻译得不好 T_T)
造个轮子
不再是围绕着 npm 客户端做基础建设了,我们决定试着从整体的角度去理解问题。要是我们去做个新的客户端, 用来解决我们曾经碰到的核心问题怎么样? 我们伦敦办公室的 Sebastian McKenzie 开始着手去顺着这个思路倒腾一下,我们很快就觉得这家伙潜力无限。
Yarn 的介绍
Yarn 是一个新的包管理器,用以替换现有的 npm 或者其他的客户端,它仍旧兼容 npm 仓库(registry)。作为现有的工作流,它具有和其他包管理器相同的特性,同时,操作更快,更安全,也更加可靠。
包管理器的主要功能就是从大的包仓库中安装一些特定目的的代码包到工程师的本地环境中。每个包或多或少地依赖于其他的包,一个标准项目在内部通常有几十个,几百个,甚至上千个依赖包。
这些依赖通常由语义化版本号来制定版本和安装。语义化版本号定义了版本标准去反映每个新版本的变动情况:是否做了巨大的 API 变动,是否添加了新的特性,还是仅仅做了 bug 的修复。然而,语义版本号依赖于包的开发者能够不出错 —— 如果依赖没有被锁定,大的变动或者新的 bug 就可能进入已被安装的依赖中。
架构
在 Node 生态中,依赖包都被放在项目的 node_modules 文件夹中。然而,这些文件结构可以和实际的依赖树不一样,因为重复的依赖被合并到一起了。npm 客户端安装依赖到 node_modules 中是不精确的。这就意味着基于指定依赖的包被安装之后,不同人之间的 node_modules 文件夹下的文件结构可以是不一样的。这种不同就会导致 ”在我的机器上就能跑“ 的这种花大力气也难以追踪的 bug。
Yarn 解决版本控制和安装不精确的问题是通过使用 lockfils 和一个确定的,可靠地安装算法。这些“锁文件”锁定安装的依赖到一个特定的版本号,并且保证每一个安装和解压缩的结果在所有机器上的 node_modules 下都有着同样的文件结构。这个写入的锁文件用了简洁的格式来表示锁定的 key,用以保证变动最小化和检查的便捷性。
安装进程分解成了下面三步:
-
分析。Yarn 分析依赖并像服务器发起请求,去递归地查找每一个依赖。
-
获取。下一步,Yarn 在全局缓存文件夹中查找包所依赖的包是否已经下载过了。如果没有下载过,Yarn 就会获取 package 的压缩包并把它放到全局缓存中,这样它就可以离线了,而且不用多次下载依赖。依赖可以放到代码控制中,作为压缩包为全局离线安装。
-
链接。最终,Yarn 通过从一起链接全局缓存中拷贝所有需要的文件到本地 node_modules 文件夹中
通过分解这几步,就有了干净的,确定的结果,Yarn 就可以并行化这些操作,最大限度地利用资源使得安装进程更快。在一些 Facebook 的项目中, Yarn 的安装进程减少了一个数量级,从几分钟缩短到了仅仅几秒钟。Yarn 也用了互斥的手段去保证多个命令行进程间不会互相碰撞污染对方。
在整个过程中,Yarn 对包的安装过程做了严格的保护。 你可以控制从哪个 package 在哪些生命周期执行命令。package 验证码也会被保存在 lockfile 中,用来保证每一次安装的都是同一个包。
特性
除了使安装更加迅速,更加简单以外,Yarn 也有一些额外的特性去进一步简化依赖管理的工作流。
-
兼容 npm 和 bower 的工作流并支持混合的仓库(registry)
-
尽量限制已安装模块的许可和许可证信息输出的手段
-
暴露出稳定的,公开的,有日志抽象能力的 JS API 提供给构建工具去使用
-
易读的,尽量少的,好看的命令行输出
生产环境中的 Yarn
在 Facebook,我们已经在生产环境中用 Yarn 了,并且干得很好。它接管了依我们很多个 JavaScript 项目的依赖和包管理工作。随着每一次迁移(migration, 更新?),我们尽量要求工程师去构建出离线的,并能加速他们工作流(的更新)。你可以看到 React Native 在不同条件下 Yarn 和 npm 的安装速度的比较。你可以在这里 找到
起步
最简单的起步方法是运行:
在你的开发工作流中用 Yarn 命令行命令替换掉 npm,无论是一个匹配得上的命令或者是新命令,相似的命令如下:
- 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 已经有了一个伟大的开始,在你的帮助下它会变得更好。
译者的话
我觉得自己翻译得不算很好,有一些句子翻译得可能略生硬,如果你觉得我哪里翻译得有问题,欢迎在下面留言让我知道,或者给我发邮件:iamhele1994#gmail.com