重要提示:本文档适用于 Yarn 1(经典版)。
有关 Yarn 2+ 文档和迁移指南,请参阅 yarnpkg.com。

离线运行 Yarn

作者:Konstantin Raev,发布于 2016 年 11 月 24 日

可重复且可靠的大型 JavaScript 项目构建至关重要。如果构建依赖于从网络下载的依赖项,那么此构建系统既不可重复,也不可靠。

Yarn 的主要优势之一是可以从位于文件系统中的文件安装 node_modules。我们称之为“离线镜像”,因为它会在第一次构建期间镜像从注册表下载的文件,并将它们本地存储以供将来构建使用。

“离线镜像”不同于 npm CLI 和 Yarn 都具有的缓存。缓存存储从注册表下载的已解压的 tarball,它们还可以是特定于实现的,并且在多个版本的 CLI 工具之间可能无效。在“离线镜像”中的 tarball 可以被任何将根据它们构建缓存的 Yarn 版本使用。在压缩时存储文件也更轻松。

让我们为简单的 JS 项目设置“离线镜像”

{
  "name": "yarn-offline",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "is-array": "^1.0.1",
    "left-pad": "^1.1.3",
    "mime-types": "^2.1.13"
  }
}

运行 yarn install 时,生成的 yarn.lock 文件针对每个依赖项都有部分内容

# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

is-array@^1.0.1:
  version "1.0.1"
  resolved "https://registry.yarnpkg.com/is-array/-/is-array-1.0.1.tgz#e9850cc2cc860c3bc0977e84ccf0dd464584279a"

left-pad@^1.1.3:
  version "1.1.3"
  resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a"

mime-db@~1.25.0:
  version "1.25.0"
  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.25.0.tgz#c18dbd7c73a5dbf6f44a024dc0d165a1e7b1c392"

mime-types@^2.1.13:
  version "2.1.13"
  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.13.tgz#e07aaa9c6c6b9a7ca3012c69003ad25a39e92a88"
  dependencies:
    mime-db "~1.25.0"

其中每个依赖项都具有带有远程 URL 的 resolved 字段。如果你删除 node_modules 文件夹并再次运行 yarn install,Yarn 将下载此锁定文件中指定的相同已解析依赖项。它甚至会通过验证每个依赖项的校验和来确保自你首次安装以来,没有人修改过文件。

然而,如果出于某种原因在构建期间无法访问这些 URL,它将失败。为了解决此问题,我们需要“离线镜像”。

设置 .yarnrc

首先我们需要设置一个目录作为我们的“离线镜像”存储,我们可以使用 yarn config 命令执行此操作

$ yarn config set yarn-offline-mirror ./npm-packages-offline-cache
yarn config v0.23.2
success Set "yarn-offline-mirror" to "./npm-packages-offline-cache".
✨  Done in 0.06s.

./npm-packages-offline-cache 是相对主文件夹的示例位置,其中所有 source.tar.gz 文件将从注册表下载到此位置。

离线镜像不具备移除 tarball 的功能。为了使缓存文件夹保持最新,你需要将以下内容添加到配置文件中

该功能仅在 0.23.0 及更高版本中可用。

$ yarn config set yarn-offline-mirror-pruning true
yarn config v0.23.2
success Set "yarn-offline-mirror-pruning" to "true".
✨  Done in 0.06s.

此操作将在你的主目录中创建一个 .yarnrc 文件。让我们将此文件移到项目根目录,以便仅对此项目使用离线镜像。

$ mv ~/.yarnrc ./

(在 yarn config 没有更新主文件夹中的文件时(极不可能发生),例如你在 Docker 容器内以 root 身份运行 yarn config,那么被更新的文件可能是其他文件。使用 yarn config list --verbose 来找到正确的文件。)

初始化新的锁定文件

移除之前生成的 node_modules,然后再次运行 yarn install

$ rm -rf node_modules/ yarn.lock
$ yarn install
yarn install v0.17.8
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Saved lockfile.
✨  Done in 0.57s.

yarn.lock 中的依赖项解析应与原始解析相同(你没有任何差异)

# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

is-array@^1.0.1:
  version "1.0.1"
  resolved "https://registry.yarnpkg.com/is-array/-/is-array-1.0.1.tgz#e9850cc2cc860c3bc0977e84ccf0dd464584279a"

left-pad@^1.1.3:
  version "1.1.3"
  resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a"

mime-db@~1.25.0:
  version "1.25.0"
  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.25.0.tgz#c18dbd7c73a5dbf6f44a024dc0d165a1e7b1c392"

mime-types@^2.1.13:
  version "2.1.13"
  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.13.tgz#e07aaa9c6c6b9a7ca3012c69003ad25a39e92a88"
  dependencies:
    mime-db "~1.25.0"

离线缓存文件将存储在先前配置的 npm-packages-offline-cache 文件夹中。每个已解析的依赖项在文件名后还会包含一个校验和,以确保没有人篡改下载的文件。

如果我们查看“离线镜像”文件夹内部,将会看到 yarn 将用于以下构建的 .tgz 文件,而无需连接网络

$ ls npm-packages-offline-cache/
is-array-1.0.1.tgz    left-pad-1.1.3.tgz    mime-db-1.25.0.tgz    mime-types-2.1.13.tgz

如何测试以确保它是离线的?

  • 使用“yarn cache clean”清除全局缓存
  • 关闭无线网络
  • 运行“yarn install –offline”。此离线标志将确保 yarn 不连接网络

简而言之,为你的项目启用“离线镜像”,你需要

  • 向 .yarnrc 文件添加“yarn-offline-mirror”配置
  • 用“yarn install”命令生成新的 yarn.lock

一些技巧和窍门

更新您的软件包

如果您想确保自己拥有干净的已缓存模块,这里为您提供了几个可以执行的步骤。

  • 首先删除软件包。确保您已将 .yarnrc 文件中的 “yarn-offline-mirror-pruning” 设置为 true
  • 在添加该软件包的更新版本之前,使用 “yarn cache clean” 清除 yarn 缓存
  • 添加软件包

“yarn-offline-mirror-pruning” 将帮助清理任何未链接的依赖项。当您添加更新的软件包时,它将首先检查 yarn 缓存,并从那里提取任何缺少的依赖项。这样可以防止 yarn 将新的 tarball 和更新的软件包一起添加回来。在为缓存模块添加任何内容之前,您需要确保 yarn 缓存是干净的。

您可以在 git 或 mercurial 存储库中检入 “离线镜像”

“离线镜像”可以在不同的构建服务器或开发机器之间共享,不受任何方式限制:一个 Box / Dropbox 文件夹,存储在源代码管理中或网络驱动器中。在 Facebook 中,离线镜像存储在我们庞大的 Mercurial “monorepo” 中。

是否将二进制文件提交到存储库中取决于您的项目的依赖项的数量和大小。例如,在总计 23MB 的 849 个 React Native 依赖项中,仅有 10% 大于 30KB。 按大小降序排列的 React Native .tgz 文件列表 React Native 中使用的所有 .tgz 文件的大小

许多 Facebook 团队,包括 React Native 团队决定检入他们的 “离线镜像”。它们都共享同一个 “离线镜像”,这意味着新项目的几乎所有依赖项通常已经检入到该文件夹中,因此将软件包存储在源代码管理中的成本随着使用该文件夹的项目增多而降低。

让我们将检入 node_modules 与检入“离线镜像”进行对比

React Native 团队过去常常检入 node_modules 文件夹,但遇到了几个限制

  • node_modules 包含 37,000 多个文件(当我们使用 npm2 时,包含 100,000 多个文件)。这样对我们的 Mercurial 存储库产生了不良的性能影响。
  • 审核更改依赖项的 Pull Request 非常困难,因为在 node_modules 中添加和删除的所有文件都会产生大量噪音,从而使代码审核变得不愉快

相比之下,使用离线镜像来更新第三方依赖项只会添加几个非常容易审核的文件

$ yarn add shelljs@0.7.0 --dev
yarn add v0.23.2
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Saved lockfile.
success Saved 4 new dependencies.
├─ interpret@1.0.1
├─ rechoir@0.6.2
└─ shelljs@0.7.0
│  └─ glob@7.1.1
✨  Done in 8.15s.

$ git diff
diff --git a/package.json b/package.json
index 4619f16..7acb42f 100644
--- a/package.json
+++ b/package.json
@@ -220,7 +220,7 @@
     "mock-fs": "^3.11.0",
     "portfinder": "0.4.0",
     "react": "~15.3.1",
-    "shelljs": "0.6.0",
+    "shelljs": "0.7.0",
     "sinon": "^2.0.0-pre.2"
   }
 }
diff --git a/yarn.lock b/yarn.lock
index 11ce116..f5d81ba 100644
--- a/yarn.lock
+++ b/yarn.lock
...
-shelljs@0.6.0:
-  version "0.6.0"
-  resolved https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.0.tgz#ce1ed837b4b0e55b5ec3dab84251ab9dbdc0c7ec
+shelljs@0.7.0:
+  version "0.7.0"
+  resolved https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.0.tgz#3f6f2e4965cec565f65ff3861d644f879281a576
+  dependencies:
+    glob "^7.0.0"
+    interpret "^1.0.0"
+    rechoir "^0.6.2"

 shellwords@^0.1.0:
   version "0.1.0"

$ git status
On branch testing-yarn
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   package.json
    modified:   yarn.lock

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    yarn-offline-mirror/interpret-1.0.1.tgz
    yarn-offline-mirror/rechoir-0.6.2.tgz
    yarn-offline-mirror/shelljs-0.7.0.tgz

no changes added to commit (use "git add" and/or "git commit -a")

您是否知道 Yarn 也被分发为单个捆绑 JS 文件,该文件位于 发布 中,可以在无需互联网连接的 CI 系统上使用?

Yarn 版本中分发的文件 **yarn-.js**(适用于 Node 5+)和 **yarn-legacy-.js**(适用于 Node 4)可以在 CI 系统中独立使用,无需进行安装。

将其检入项目的存储库并将其用于构建脚本即可

node ./yarn-0.23.2.js install

这对使用多个操作系统的团队非常方便,他们希望针对 Yarn 进行原子更新。

这将会变得更好

“离线镜像”是在 Yarn 的开发周期早期阶段实现的,我们正在通过向后兼容的方式对其进行完善

  • resolved 字段同时用于脱机镜像路径和注册表 URI。这意味着 React Native 团队在内部使用的 yarn.lock 文件与开源社区无法共享,因为 React Native 团队并未将脱机镜像与 React Native 的开源版本进行同步。问题
  • 对于 Yarn 的未来版本,正在考虑一个改进的工作流程。它没有明显的差异,但一些设置和锁定文件可能会有变化。