2017 年 8 月 2 日发布,作者:Konstantin Raev
项目往往会随着时间发展,有时项目中的某些部分可在其他项目中的其他地方使用。例如,作为一种通用测试工具的Jest催生了许多包,其中之一是jest-snapshot,现在用于其他项目,如snapguidist和chai-jest-snapshot。
单模块仓库
那些尝试将项目拆分成多个包的人知道一次跨多个包进行更改有多困难。为了使流程更简单,一些大型项目采用了单模块仓库方法或多包仓库,这减少了跨包编写代码的负担。
JavaScript 开发人员日常使用的多个项目都作为单模块仓库管理:Babel、React、Jest、Vue、Angular。
然而,有时仅将项目部分内容分到自己的文件夹中是不够的。测试、管理依赖项和发布多个包很快会变得复杂,许多此类项目采用诸如Lerna之类的工具,以简化单模块仓库工作。
Lerna
Lerna 是一种优化使用 git 和 npm 管理多包仓库的工作流的工具。内部使用Yarn或 npm CLI 来引导项目(即,为每个包安装所有第三方依赖项)。简而言之,Lerna 为项目内的每个包调用yarn/npm install
,然后在引用彼此的包之间创建符号链接。
作为包管理器的一个包装器,Lerna 无法有效地处理 node_modules 的内容
- Lerna 为每个包多次调用
yarn install
,这会产生开销,因为每个package.json
都被认为是独立的,它们不能彼此共享依赖项。这导致每个 node_modules 文件夹出现大量重复,这些文件夹通常使用相同的第三方包。 - 安装完成后,Lerna 手动在引用彼此的包之间创建链接。这在 node_modules 中引入了包管理器可能不知道的不一致性,因此从包内运行
yarn install
可能会破坏 Lerna 管理的元结构。
诸如此类的问题使我们确信,作为包管理器开发人员,我们应该直接在 Yarn 中支持多包仓库。从Yarn 0.28开始,我们很高兴地宣布我们在工作区功能下支持此类仓库。
推出纱线工作区
纱线工作区是一项功能,它允许用户一次性从单个根 package.json 文件的子文件夹中多个 package.json 文件中安装依赖项。
将工作区设为 Yarn 原生功能可通过防止工作区中包出现重复来实现更快、更轻量的安装。Yarn 还能在相互依赖的工作区之间创建符号链接,并将确保所有目录的一致性和正确性。
设置工作空间
启用 Yarn 1.0 工作空间默认情况下处于启用状态,你可能无需设置以下配置。了解以下位置的更新步骤 https://yarn.npmjs.net.cn/lang/en/docs/workspaces/
如欲开始,用户必须通过运行以下命令,在 Yarn 中启用工作空间
yarn config set workspaces-experimental true
将其添加到 OS 主文件夹中的 .yarnrc
文件中。在收集社区反馈期间,Yarn 工作空间仍会被视为实验状态。
我们以 Jest 为例,并针对该结构设置 Yarn 工作空间。事实上,它已经 通过 PR 进行了操作,并且 Jest 使用 Yarn 来引导其软件包已有一段时间。
Jest 的项目结构是面向开源 JavaScript 单一存储库的典型结构。
| jest/
| ---- package.json
| ---- packages/
| -------- jest-matcher-utils/
| ------------ package.json
| -------- jest-diff/
| ------------ package.json
...
顶级 package.json
定义该项目的根,包含其他 package.json 文件的文件夹则是工作空间。工作空间通常发布到注册表(如 npm)。虽然根不应视为软件包来使用,但它通常包含其他项目无法使用的粘合代码或业务特定代码,所以我们将其标为“专用”。
以下示例是一个简化的根 package.json
,可启用该项目的 Workspaces 并定义项目构建和测试环境所需的三方软件包。
{
"private": true,
"name": "jest",
"devDependencies": {
"chalk": "^2.0.1"
},
"workspaces": [
"packages/*"
]
}
为了简单起见,我将介绍两个小型工作空间包
-
jest-matcher-utils 工作空间
{ "name": "jest-matcher-utils", "description": "...", "version": "20.0.3", "license": "...", "main": "...", "browser": "...", "dependencies": { "chalk": "^1.1.3", "pretty-format": "^20.0.3" } }
-
依赖于 jest-matcher-utils 的 jest-diff 工作空间
{ "name": "jest-diff", "version": "20.0.3", "license": "...", "main": "...", "browser": "...", "dependencies": { "chalk": "^1.1.3", "diff": "^3.2.0", "jest-matcher-utils": "^20.0.3", "pretty-format": "^20.0.3" } }
Lerna 这样的 包装会首先分别为每个 package.json
运行 yarn install
,然后为互相依赖的软件包运行 yarn link
。
如果我们使用该方法,我们将得到以下文件夹结构
| jest/
| ---- node_modules/
| -------- chalk/
| ---- package.json
| ---- packages/
| -------- jest-matcher-utils/
| ------------ node_modules/
| ---------------- chalk/
| ---------------- pretty-format/
| ------------ package.json
| -------- jest-diff/
| ------------ node_modules/
| ---------------- chalk/
| ---------------- diff/
| ---------------- jest-matcher-utils/ (symlink) -> ../jest-matcher-utils
| ---------------- pretty-format/
| ------------ package.json
...
如您所见,三方依赖存在冗余。
在启用工作空间的情况下,Yarn 可以生成更优化的依赖项结构,当您在项目的任何位置运行通常 yarn install
时,您将获得以下 node_modules。
| jest/
| ---- node_modules/
| -------- chalk/
| -------- diff/
| -------- pretty-format/
| -------- jest-matcher-utils/ (symlink) -> ../packages/jest-matcher-utils
| ---- package.json
| ---- packages/
| -------- jest-matcher-utils/
| ------------ node_modules/
| ---------------- chalk/
| ------------ package.json
| -------- jest-diff/
| ------------ node_modules/
| ---------------- chalk/
| ------------ package.json
...
诸如 diff
、pretty-format
以及 jest-matcher-utils
的符号链接已提升至根 node_modules 目录,从而使安装变得更快更小。但是,软件包 chalk
无法移动至根,因为根已依赖于不同版本的不兼容 chalk
。
上述两种结构均兼容,但后者是更优的,同时在 Node.js 模块解决逻辑上仍然正确。
对于热衷 Lerna 的用户,这类似于通过 --hoist
标志引导代码。
如果您在 jest-diff
工作空间内运行代码,那么它将能够解决其所有依赖项
- require(‘chalk’) 解决为
./node_modules/chalk
- require(‘diff’) 解决为
../../node_modules/diff
- require(‘pretty-format’) 解决为
../../node_modules/pretty-format
- require(‘jest-matcher-utils’) 解决为
../../node_modules/jest-matcher-utils
那是一个指向../packages/jest-matcher-utils
的符号链接
管理工作空间的依赖性
如果您想修改工作空间的依赖性,只需在工作空间文件夹中运行相应的命令
$ cd packages/jest-matcher-utils/
$ yarn add left-pad
✨ Done in 1.77s.
$ git status
modified: package.json
modified: ../../yarn.lock
请注意,工作空间没有其自己的 yarn.lock 文件,并且根 yarn.lock 包含所有工作空间的所有依赖性。当您想更改工作空间内的依赖项时,根 yarn.lock 将会更改,工作空间的 package.json 也会更改。
与 Lerna 集成
Yarn 工作空间是否使 Lerna 过时?
当然不是。Yarn 工作区可以轻松与 Lerna 集成。
Lerna 提供的不仅仅是引导项目,而且还拥有一个用户社区,他们可以根据自己的需求微调 Lerna。
从 Lerna 2.0.0 开始,当你运行 Lerna 命令时传递标志 --use-workspaces
,它将使用 Yarn 引导项目,它还将使用 package.json/workspaces
字段查找包,而不是 lerna.json/packages
。
Lerna 是如何针对 Jest 配置的
{
"lerna": "2.0.0",
"npmClient": "yarn",
"useWorkspaces": true
}
Jest 依赖 Yarn 引导项目,依赖 Lerna 运行发布命令。
接下来是什么?
Yarn 工作区是包管理器可以为管理单一存储库所做的第一步,因为单一存储库已经成为代码共享的一种更常见的解决方案。
与此同时,我们不想将所有可能的单一存储库功能放入 Yarn。我们希望 Yarn 保持专注和精简,这意味着 Yarn 和类似 Lerna 的项目将会继续协同工作。
我们的下一个目标是完成 Yarn 1.0,这个目标旨在总结我们在过去一年中在 Yarn 上所做的工作,并认识到 Yarn 已经变得有多么可靠。我们还将分享我们对接下来希望为 Yarn 构建什么想法。
敬请期待。