重要信息:此文档介绍 Yarn 1(经典版)。
有关 Yarn 2+ 文档和迁移指南,请参阅 yarnpkg.com。

Yarn 确定性

Sebastian McKenzie 于 2017 年 5 月 31 日发布

Yarn 对自己的一个宣传是,它能使您的软件包管理“确定性”。但这究竟是什么意思呢?本篇博文重点介绍了 Yarn 和 npm 5 如何都具有确定性,但它们提供的具体保证和它们选择的折衷方案有所不同。

什么是确定性?

在 JavaScript 软件包管理中,确定性的含义是给定 package.json 和配套锁定文件后,始终获得完全相同的 node_modules 文件夹。

Yarn 确定性的哪些因素有保证?

锁定文件

只要您的所有团队成员都使用相同版本的 Yarn,Yarn 就是完全确定的。在 Yarn 和 npm 5 中,确定性通过包含整个树信息的锁定文件来确保。但是,这两个项目之间的锁定文件格式不同。我们尚未公开讨论我们选择此格式的原因,所以我们希望带您详细了解一下

如果您曾经看过 yarn.lock,那么您应该非常熟悉以下结构

has-flag@^1.0.0:
  version "1.0.0"
  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"

supports-color@^3.2.3:
  version "3.2.3"
  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
  dependencies:
    has-flag "^1.0.0"

这是通过运行命令 yarn add supports-color 生成的 yarn.lock 文件。此文件包含我们项目使用的 supports-color 版本以及其所有子依赖项的确切版本。

有了此锁定文件,我们可以确保 supports-color 所依赖的 has-flag 版本始终是相同版本。

但是,yarn 锁定文件不包含一个关键信息,即树中每个依赖项的提升和位置。例如,给定 yarn.lock,除非有它的配套 package.json,否则无法确定顶级依赖项是什么。即使知道了顶级软件包,我们仍然无法推断出提升位置中的软件包应是什么。

在实践中,这意味着 node_modules 中软件包的位置是在 Yarn 内部计算的,这导致不同版本的用户之间的 Yarn 具有不确定性。

我们这样做是因为这种锁定文件格式非常适合差异化。也就是说,可以轻松地对锁定文件中的更改进行人工审查。我们使用自定义格式而不是 JSON 的原因以及将所有内容放在顶层的原因是为了便于阅读和审查。合并冲突通常由版本控制自动处理,并且它减少了反复工作。

提升保证

即使 Yarn 提升在不同版本之间可能有所不同,但是当使用相同版本的 Yarn 时,我们仍然对提升做出非常有力的保证。其中最重要的保证是,省略 optionalDependenciesdevDependencies 等环境依赖项仍然会影响常规 dependencies 的位置。

哇哦,那是一大堆依赖项专业术语。这实际上是什么意思?

您可以在 package.json 中声明多种类型的依赖项。其中两种是始终安装的普通 dependencies,还有 devDependencies,它们仅在您在存在 package.json 文件的目录中运行 npm installyarn 时安装。

由于这些特性,可以在省略依赖项后对 node_modules 采用不同的布局。但在确定 node_modules 中的位置时,Yarn 仍会考虑所有依赖项,因此即使未安装这些依赖项,它们仍然会影响其他依赖项提升的位置。这一点很重要,因为生成环境和开发环境中软件包提升位置的方差会导致十分奇怪但难以发现的错误。

注意:这种保证不是 Yarn 特有的功能,npm 5 同样可以实现。

与 npm 5 相比有何异同点?

npm 5 引入了对 shrinkwrap 特性的重新设计,称为 package-lock。此文件中包含创建 node_modules 所需的所有信息以及所有提升信息。之前 yarn.lock 文件的 npm 版本为

{
  "name": "react-example",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "dependencies": {
    "has-flag": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
      "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo="
    },
    "supports-color": {
      "version": "3.2.3",
      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
      "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY="
    }
  }
}

请注意,这里第一个依赖项对象中列出的所有软件包都已提升。这意味着 npm 可以仅使用 package-lock 作为真相的来源,以构建最终依赖图,而 Yarn 则需要附带的 package.json 文件作为种子。

这意味着 npm 在 npm 版本之间更好地确保了提升位置,但代价是锁文件更密集。当前没有计划如何支持 Yarn 中的 package-lock.json,因为围绕锁文件互操作性的故事尚不明朗。不过,你可以想象 Yarn 未来同时支持这两种格式,并对其串行更新。我们非常重视社区反馈,并鼓励提交建议,说明如何实现这种功能,作为 RFC

结束语

每种锁定文件格式都有不同的权衡利弊,而且似乎没有一种完美的格式没有缺点。在决定使用哪个包管理器时,重要的是要评估你在寻找什么类型的保证。

npm 5 的跨版本保证更强,并且具有确定性更强的锁文件,但 Yarn 只有在使用相同版本时才有这些保证,以支持更适合于审阅的更轻量级锁文件。有可能存在一个同时具备两者优点的锁文件解决方案,但目前这是生态系统的当前状态,未来可能会发生融合。

希望这篇文章突出了 Yarn 和 npm 之间不同的确定性保证,并帮助你决定对公司或项目更合适的内容。