Konstantin Raev于2017年8月2日发布
项目往往会随着时间的推移而增长,有时,项目的某些部分在其他项目中也会有用。例如,Jest作为一种通用的测试工具,产生了许多包,其中之一就是Jest snapshot,它现在被用于其他项目,如snapguidist和chai Jest snapshot。
Monorepos
那些尝试过将一个项目拆分为多个包的人知道一次在多个包中进行更改是多么困难。为了简化这个过程,一些大型项目采用了monorepo方法或多包存储库,这减少了跨包编写代码的负担。
JavaScript开发人员每天使用的几个项目都作为monorepo进行管理:Babel、React、Jest、Vue、Angular。
然而,将项目的各个部分分离到它们自己的文件夹中有时是不够的。测试、管理依赖项和发布多个包很快就变得复杂起来,许多这样的项目采用了Lerna等工具来简化monorepos的工作。
Lerna
Lerna是一个工具,它优化了使用git和npm管理多包存储库的工作流。在内部,它使用Yarn或npm cli引导(即为每个包安装所有第三方依赖项)一个项目。简而言之,Lerna为项目中的每个包调用yarn/npm install,然后在相互引用的包之间创建符号链接。
作为包管理器的包装器,Lerna无法有效地操作节点模块的内容:
- 对于每个包,Lerna多次调用yarn install,这会造成开销,因为package.json被认为是独立的,它们不能彼此共享依赖关系。这会导致每个node_modules文件夹出现大量重复,这些文件夹通常使用相同的第三方软件包。
- 安装完成后,Lerna手动在包之间创建相互引用的链接。这会在node_模块内部引入包管理器可能不知道的不一致性,因此从包中运行yarn install可能会破坏Lerna管理的元结构。
这样的问题使我们,作为包管理器开发人员,确信我们应该直接支持多包存储库。从Yarn 0.28开始,我们很高兴与大家分享,我们在工作区特性下支持这样的存储库。
介绍Yarn工作区
Yarn工作区是一个允许用户从多个package.json子文件,单个根的package.json文件,安装所有依赖,一气呵成。
将工作区设为Yarn的本机,通过防止跨工作区的包复制,可以实现更快、更轻的安装。Yarn还可以在相互依赖的工作区之间创建符号链接,并确保所有目录的一致性和正确性。
Setting up Workspaces
从 Yarn 1.0开始工作区在默认情况下是启用的,您可能不需要设置下面的配置。请参阅以下位置的更新步骤https://yarnpkg.com/lang/en/docs/workspaces/
要开始使用,用户必须通过运行以下命令在Yarn中启用工作区:
yarn config set workspaces-experimental true
它会将实验性的工作区添加到操作系统主文件夹中的.yarnrc文件中。当我们收集社区的反馈时,yarn工作区仍然被认为是实验性的。
让我们以Jest为例,为该结构设置yarn工作区。事实上,这已经在一次PR中完成了,Jest已经用Yarn来引导它的包了一段时间。
Jest的项目结构是开源JavaScript monorepo的典型结构。
| jest/
| ---- package.json
| ---- packages/
| -------- jest-matcher-utils/
| ------------ package.json
| -------- jest-diff/
| ------------ package.json
...
最高层package.json定义项目的根目录,以及与其他package.json文件夹就是工作空间。工作区通常发布到npm这样的注册中心。虽然根不应该作为一个包使用,但它通常包含胶水代码或业务特定的代码,这对于与其他项目共享是不有用的,这就是为什么我们将其标记为“私有”的原因。
下面的示例是一个简化的根package.json它为项目启用工作区,并定义项目构建和测试环境所需的第三方包。
{
"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-diff 工作区依赖 jest-matcher-utils:
{ "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安装时,您将得到以下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和symlink to jest matcher utils等包被提升到根节点的 node_modules目录中,使安装变得更快、更小。但是包 chalk无法移动到根目录,因为根目录已经依赖于另一个不兼容的粉笔版本。
上述两种结构都是兼容的,但后者更为优化,同时仍然是正确的节点.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进行了微调,以满足他们的需求。
从lerna2.0.0开始,当您传递标志--useworkspaces当运行Lerna命令时,它将使用Yarn引导项目,并且它也将使用package.json/workspaces字段来查找包而不是lerna.json/包.
以下是Lerna为Jest配置的方式:
{
"lerna": "2.0.0",
"npmClient": "yarn",
"useWorkspaces": true
}
Jest依赖Yarn引导项目,并依赖Lerna来运行publish命令。
下一步是什么?
Yarn工作区是包管理器管理monorepos的第一步,因为monorepos成为代码共享的更常见的解决方案。
同时,我们也不想把所有可能的monorepo特性放在Yarn。我们希望保持Yarn的专注和精益,这意味着Yarn和像Lerna这样的项目将继续合作。
我们的下一个目标是最终确定Yarn 1.0,这意味着总结我们过去一年在Yarn方面所做的工作,并认识到Yarn已经变得多么可靠。我们还将分享我们对Yarn下一步要做什么的想法。
敬请期待。