在 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),我们很荣幸把这个工具开源分享到社区中。 介绍 YarnYarn 是一个新的包管理器,用于替代现有的 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 除了让安装过程变得更快与更可靠,还添加了一些额外的特性,从而进一步简化依赖管理的工作流。
Yarn 用于生产环境我们已经在 Facebook 中把 Yarn 用于生产环境,并且效果非常理想。Yarn 有效地管理了许多 JavaScript 项目的包依赖关系。在每次迁移时,构建都可以离线进行,因此加速了工作流程。我们基于 React Native 在不同条件下进行安装时间测试,比较了 Yarn 与 npm 的性能,具体参见这里。 
起步最简单的起步方法是: npm install -g yarnpkgyarn 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 的未来将会更加美好。 原文链接 : Yarn: A new package manager for JavaScript 稿源:达仔blog |