代码共享加快了开发速度,这不是什么秘密。而没有比monorepo更好的团队合作方式了--只要你有合适的工具。
什么是Lerna
Lerna是一个用于JavaScript项目的单版本管理器。它可以帮助你把一个大型代码库分割成可独立部署的包。Lerna处理发布过程中的每一个步骤--从下载依赖关系、将包连接在一起,到测试和发布更新的包到NPM注册表。
通过运行在npm和Yarn等传统工具之上,Lerna可以了解软件库中的软件包是如何相互连接的。不用说,这使得库中的代码共享变得非常容易。

谁在使用Lerna
你不需要相信我的话。Lerna是令人难以置信的流行项目的开发周期的一个组成部分,如Babel、Facebook的Jest、Gatsby、Google的AngularJS、EmberJS和MeteorJS。

Lerna的版本管理模式
在使用Lerna之前,你需要为你的仓库决定一个版本管理方案。Lerna支持两种模式:固定和独立。
在固定模式下,Lerna为版本库中的每个包都保持相同的版本。更新的软件包将总是被一起提升到相同的版本。这是默认模式。
独立模式意味着每个软件包都是单独的版本,允许维护者独立地提升版本。在发布时,你会被提示如何处理每个更新的软件包。
创建一个新的monorepo
我们有一个小的JavaScript monorepo演示,可以在这里玩玩。你可以在跟随本教程的过程中自由地克隆它,并将其分叉:
TomFern / semaphore-demo-monorepo-javascript
我们先用lerna init 生成一个Lerna配置。如果你喜欢独立模式,在命令中加上--independent :
$ lerna init
Creating package.json
Creating lerna.json
Creating packages directory
Initialized Lerna files
将你所有的应用程序、库、子项目和共享代码移到packages 文件夹中。每个项目都应该有一个package.json ,最好还有一个锁文件:
$ lerna import api
$ lerna import web
Lerna现在应该开始检测软件包了。在这个演示中,有两个包:一个GraphQL API服务和一个Next.js静态网站:
$ lerna ls
api
web
found 2 packages
使用lerna bootstrap 来下载 NPM 依赖项,并在仓库中交叉链接软件包:
$ lerna bootstrap
现在你应该能够用lerna run ,运行每个包中发现的所有测试。试试这些测试,以确保它们作为一个群体工作得很好--演示中包含单元和集成测试:
$ lerna exec npm run lint
$ lerna exec npm dev &
$ lerna exec npm test
$ lerna exec npm run test integration
发布前的检查
我们将把这些包发布到npmjs.com。要尝试这一部分,你至少需要在该服务上有一个免费账户。登录后,生成一个自动化令牌,并将显示的值复制到安全的地方。我们将在几分钟内需要它。
当你在做这个的时候,如果你还没有,用npm登录来验证你的机器。
NPM要求所有的包都有唯一的标识符,所以我们不能使用演示版本库中的名字。因此,通过编辑它们各自的packages.json ,重新命名这些包。
要使包的名字独一无二,最简单的方法可能是通过范围化。你可以用你的NPM用户名作为包的前缀,使其范围化。在我的例子中,我会像这样修改packages.json 的前几行:
"name": "@tomfern/api",
"publishConfig": {
"access": "public"
}
提交到Git仓库的修改是干净的:
$ git add lerna.json package.json packages
$ git commit -m "install lerna, ready to publish"
发布你的包
发布软件包是一个两步的过程。首先,Lerna 将所有的修改推送到远程仓库并创建一个 Git 标签。然后,它将更新部署到NPM。Lerna使用Git标签来标记发布和跟踪变化。
第一步是通过lerna version 来完成的:
$ lerna version
? Select a new version (currently 0.0.0) (Use arrow keys)
Patch (0.0.1)
Minor (0.1.0)
Major (1.0.0)
Prepatch (0.0.1-alpha.0)
Preminor (0.1.0-alpha.0)
Premajor (1.0.0-alpha.0)
Custom Prerelease
Custom Version
Lerna想知道下面的版本号应该是什么。使用语义版本管理,我们必须决定如何给这个版本编号。惯例是使用:
- patch(1.2**.X**):当它没有引入行为变化时。例如,修复一个错误。
- minor(1**.X**.3): 当这个版本包括向后兼容的变化。
- major(X**.2.3):当版本引入了破坏性的变化。
在进行改变之前,Lerna会要求确认:
Changes:
- @tomfern/api: 1.0.0. => 1.2.3
- @tomfern/web: 1.0.0 => 1.2.3
? Are you sure you want to create these versions?
在挑选了一个版本后,Lerna会创建一个标签并推送它:
$ lerna publish from-git
Found 2 packages to publish:
- @tomfern/api => 1.2.3
- @tomfern/web => 1.2.3
? Are you sure you want to publish these packages?
你也可以在一个命令中结合版本管理和发布:
$ lerna publish patch
更改-检测
Lerna了解Git和JavaScript。因此,它可以检测到一个包何时发生了变化。要查看哪些包应该被发布,请使用:
$ lerna changed
Looking for changed packages since v1.2.3
@tomfern/api
found 1 package ready to publish
你可以通过lerna diff 找到每个包的变化细节。
配置CI/CD管道
在这一部分,你需要一个Semaphore账户。如果你还没有,你可以在GitHub上免费创建一个试用账户。
现在的诀窍是在CI/CD管道中自动完成所有这些过程,计划是这样的
- 安装并缓存所有的依赖项
- 运行所有测试
- 如果我们是在一个有标签的版本上,发布软件包
登录Semaphore后,点击创建新项目来添加一个新项目:

开始一个新项目
选择分叉的版本库:

选择存储库
最后,选择 "单一工作 "并点击自定义:

挑选一个启动工作流程
安装作业
构建阶段引导版本库并缓存下载的依赖项。我们使用lerna bootstrap ,然后使用npm exec cache ,将node_modules 的内容存储在Semaphore缓存中:
npm install --global lerna
checkout
lerna exec -- cache restore node-modules-\$LERNA_PACKAGE_NAME-$SEMAPHORE_GIT_BRANCH,node-modules-\$LERNA_PACKAGE_NAME
lerna bootstrap
lerna exec -- cache store node-modules-\$LERNA_PACKAGE_NAME-$SEMAPHORE_GIT_BRANCH,node-modules-\$LERNA_PACKAGE_NAME node_modules

构建块
测试块
任何持续集成都不应该缺少测试。我们的演示包括三种类型的测试:
- Linter:运行eslint来运行静态代码分析测试。
- 单元测试:执行所有包中的单元测试。
- 集成测试:执行集成测试套件。
点击添加块,在右窗格中向下滚动到序幕。序幕在块中的任何工作之前执行。键入以下命令以检索缓存的依赖关系:
npm install --global lerna
checkout
lerna exec -- cache restore node-modules-\$LERNA_PACKAGE_NAME-$SEMAPHORE_GIT_BRANCH,node-modules-\$LERNA_PACKAGE_NAME
lerna bootstrap
测试作业都是单行的,这就是林特:
lerna run lint
在该块中再创建两个作业,一个用于单元测试:
lerna run test
和一个用于集成测试:
lerna run test-integration

测试块
点击 "运行工作流">开始,尝试管道。

启动工作流以保存它
持续部署
这里的目标是使用持续交付将包发布到NPM注册中心。
我们将首先在Semaphore上创建一个秘密,点击主菜单上的设置。

然后进入secrets,按create secret。在值中,键入NPM_TOKEN ,并填入先前生成的自动化令牌。保存该秘密:

回到Semaphore的工作流程,点击编辑工作流程,打开编辑器。
点击添加推广,创建第二个管道。启用自动推广复选框,并输入这一行,选择有标签的发布:
tag =~ '.*' AND result = 'passed'

点击交付管道上的第一个作业,在作业中使用以下命令:
npm install --global lerna
checkout
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
lerna exec -- cache restore node-modules-\$LERNA_PACKAGE_NAME-$SEMAPHORE_GIT_BRANCH,node-modules-\$LERNA_PACKAGE_NAME node_modules
lerna bootstrap
lerna publish from-git --no-git-tag-version --no-push --yes
向下滚动并检查先前创建的NPM secret:

发布块使用NPM的秘密与auth token
保存流水线。它将再运行一次,但不会发生发布。接下来,尝试从你自己的机器上使用lerna version 更新其中一个包。
$ git pull origin main
$ lerna version patch
当Lerna推送有标签的版本时,流水线就会启动。

用持续交付发布软件包
基于改变的测试
Lerna会自己检测哪些包在上次发布后发生了变化,并只发布新版本。但这个功能只适用于发布,而不是测试。
虽然Lerna不支持基于变化的测试,但Semaphore支持。而且它很容易配置。诀窍在于change_in ,该函数计算文件夹和文件的变化。让我们看看如何使用它。
要使用change_in,你需要为每个软件包或软件包组创建单独的测试路径。换句话说,你必须编辑 "测试 "中的作业,使它们只对其中一个使用--scope 选项的软件包进行操作。作为一个例子,这使得lint作业只在@tomfern/api 包上运行。
lerna run lint --scope @tomfern/api
在其余的测试工作中重复这一改变:
lerna run test --scope @tomfern/api
lerna run test-integration --scope @tomfern/api

现在 "测试API "块在一个包上运行测试
现在为另一个包创建第二个测试块,并使其依赖于 "Bootstrap "块。这一次,使用--scope 来选择另一个包。
现在,神奇的技巧出现了。向下滚动,直到你到达 "跳过/运行条件",并选择在条件满足时运行此块。例如,当/packages/api 文件夹中的文件发生变化时,会触发以下条件。
change_in('/packages/web/', { default_branch: 'main'})

测试WEB "块只在/packages/web中的东西发生变化时运行
如果你的版本库的默认分支是master ,你可以省略{ default_branch: 'main' } 部分。
对api 包重复同样的步骤。
change_in('/packages/api/', { default_branch: 'main'})
点击运行工作流程,保存设置并尝试管道。如果使用得当,变更检测可以大大加快流水线的速度。

变更检测跳过影响未改变代码的块
接下来的步骤
一如既往,仍有一些改进的余地。例如,你可能想使用Lerna的软件包吊装来减少node_modules 。
请记住,如果你愿意,Lerna可以与Yarn联手。你可以通过将这些行添加到lerna.json ,从npm 切换到yarn 。
"npmClient": "yarn",
"useWorkspaces": true
这样做的好处之一是,我们可以使用Yarn工作空间来完全避免使用node_modules 。
就这样了
单核处理器越来越受欢迎。在很大程度上,这要归功于改进的工具支持。如果你在一个仓库里有很多JavaScript包,并且想把它们发布到NPM,Lerna就是合适的工具。
你是一个JavaScript开发者吗?我们为你准备了很多令人兴奋的材料。