【译】 Lerna + yarn Workspaces 管理 Monorepo 入门指南

2,922 阅读8分钟

Source: jsilvax. A Beginner's Guide to Lerna with Yarn Workspaces. Oct/6/2018

当结合在一起时,Lerna和Yarn Workspaces可以简化和优化对多包仓库的管理。 Lerna 通过提供有用的实用命令来处理跨多个包的任务执行,使版本管理和将包发布到NPM Org中成为一种轻松的体验。 Yarn Workspaces 管理我们的依赖关系。它不需要多个node_modules目录,而是智能地优化了依赖关系,将他们一并安装,并允许在一个monorepo中交叉链接依赖关系。Yarn Workspaces提供了像Lerna这样的工具来管理多包仓库。

为了开始,让我们启用Yarn Workspaces吧

yarn config set workspaces-experimental true

现在我们可以通过创建一个模拟项目来说明这些概念了

mkdir my-design-system && cd my-design-system

然后,我们初始化项目

yarn init

并将Lerna添加为开发依赖。

yarn add lerna --dev

然后你会想要初始化Lerna,这将创建一个lerna.json和一个包目录

lerna init

为了设置Lerna开启Yarn工作空间,我们需要配置lerna.json。 让我们添加yarn作为我们的npm客户端,并指定我们使用yarn工作空间。在本教程中,我们将独立地版本化我们的包。

// lerna.json
{
  "packages": ["packages/*"],
  "version": "independent",
  "npmClient": "yarn",
  "useWorkspaces": true
}

此时我们应该只有一个根 package.json。在这个根 package.json中,我们需要添加workspaces和private为true。将private设置为true将阻止根项目被发布到NPM。

// package.json
{
  "name": "my-design-system",
  "private": true,
  "workspaces": [
     "packages/*"
  ]  
}

创建一个新包的流程

需要在包目录下创建新的包。让我们创建一个模拟表单包

cd packages

一旦我们进入了正确的目录,我们就可以创建并cd到我们的新包中了

mkdir my-design-system-form && cd my-design-system-form

然后我们通过运行 yarn init 来创建一个新的package.json

yarn init

新包的名称应该遵循我们的NPM Org scope命名方式,例如:@my-scope-name。 同样重要的是,新的包要从0.0.0这样的版本开始,因为一旦我们使用Lerna进行第一次发布,它就会发布成0.1.0或1.0.0。

// package.json
{
  "name": "@my-scope-name/my-design-system-form",
  "version" : "0.0.0",
  "main" : "index.js"
}

如果您有一个支持私有包的NPM Org账户,您可以在您的模块的独立包.json中添加以下内容。

"publishConfig": {
    "access": "restricted"
}

将本地的兄弟依赖关系添加到特定的包中

现在我们知道了创建新包的流程,假设说我们最后的结构是这样的。

my-design-system/
    packages/
        my-design-system-core/
        my-design-system-form/
        my-design-system-button/

如果我们想把my-design-system-button作为依赖关系添加到my-design-system-form中,并让Lerna将它们进行符号链接,我们可以通过cd到该包中来实现。

cd my-design-system-form 

然后运行以下内容。

lerna add @my-scope-name/design-system-button --scope=@my-scope-name/my-design-system-form

这将更新@my-scope-name/my-design-system-form的package.json。 我们的package.json应该是这样的。

// package.json
{
  "name": "@my-scope-name/my-design-system-form",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "@my-scope-name/my-design-system-button": "^1.0.0"
  }
}

现在,你可以在index.js中引用这个本地依赖关系,如

import Button from '@my-scope-name/my-design-system-button';

为所有的包添加一个 "共同 "的依赖关系

做法和前面的命令类似。不过这是针对/packages/* 的。不管你要加的依赖是本地的同级依赖还是来自NPM的依赖,都没关系。

lerna add the-dep-name

如果你有常见的开发依赖,最好在 workspace 的 root package.json中指定。例如,可以是Jest、Husky、Storybook、Eslint、Prettier等依赖项

yarn add husky --dev -W

*添加-W标志,就可以明确表示我们要把依赖关系添加到工作区根目录。

删除依赖

如果有一个所有包都使用的依赖,但你想删除,Lerna有exec命令,可以在每个包中运行一个任意命令。有了这些知识,我们就可以使用exec来删除所有包的依赖关系。

lerna exec -- yarn remove dep-name

运行测试

Lerna提供了run命令,它将在每个包含了npm脚本的包中运行该脚本。 例如,假设我们所有的包都遵循my-design-system-form的结构。

my-design-system-form/
    __tests__/
        Form.test.js

在每个package.json中,我们都有测试的npm脚本。

"name": "@my-scope-name/my-design-system-form",
"scripts": {
    "test": "jest"
}

然后Lerna可以通过运行每个测试脚本来执行。

lerna run test --stream

*-stream 这个flag提供子进程的输出。

发布到NPM

手动

首先,你需要确保你已经登录了。你可以通过以下操作来验证你是否已经登录。

npm whoami // myusername

如果你没有登录,请运行以下内容并按照提示操作。

npm login

登录后,您可以通过运行Lerna发布。

lerna publish

Lerna会提示你更新版本号。

自动

Lerna支持使用Conventional Commits Standard在CI环境中自动进行语义版本管理。 这使开发人员能够像下面这样提交

git commit -m "fix: JIRA-1234 Fixed minor bug in foo"

然后在CI环境中,包的版本可以根据上面的提交更新并发布到NPM。这可以通过配置你的CI环境来完成。

lerna publish --conventional-commits --yes 

如果你不想传递flag,可以在你的lerna.json文件中添加以下内容。

"command": {
    "publish": {
       "conventionalCommits": true, 
       "yes": true
    }
}

强制执行 Conventional Commits

如果你想强制执行 Conventional Commits 标准,我建议在项目的ROOT中加入Commitlint。

yarn add @commitlint/cli @commitlint/config-conventional husky cross-env --dev

然后在根package.json中创建一个发布脚本

"scripts": {
    "release": "cross-env HUSKY_BYPASS=true lerna publish"
}

这个发布脚本将在CI环境中运行。请注意,我们在 lerna.json 文件中配置了传统的提交和 "yes "标志。由于这个CI环境将会把版本的变更提交,我们不希望触发提交消息的inting。我们通过添加一个名为HUSKY_BYPASS的环境变量来实现,我们将使用cross-env将其设置为true。 我们还需要在root package.json中添加进一步的配置。

"husky": {
    "hooks": {
    "commit-msg": "[[ -n $HUSKY_BYPASS ]] || commitlint -E HUSKY_GIT_PARAMS"
    }
},
"commitlint": {
    "extends": ["@commitlint/config-conventional"]
}

对于husky,我们添加了一个commitlint/config-conventional的commit-msg钩子,它将检查我们在上面添加的HUSKY_BYPASS环境变量,如果这个变量是假的,那么我们通过@commitlint/config-conventional来精简提交消息。

分离版本控制与发布

如果出于任何原因,你想完全掌控版本控制,Lerna有能力将版本控制和发布分成两个命令。 你可以手动运行。

lerna version

然后按照提示更新各个版本号。 然后你就可以有一个步骤,读取最新的标签(是手动更新的)发布到NPM。

lerna publish from-git --yes

多贡献者参与的本地开发

每当有新的贡献者对你的项目进行git克隆,或者你需要拉取你团队的最新变化时,你必须运行yarn命令。

yarn

在大多数的Lerna教程中,提倡使用lerna bootstrap命令,然而当启用yarn工作空间时,这是不必要的,也是多余的

lerna bootstrap when you're using Yarn workspaces is literally redundant? All lerna bootstrap --npm-client yarn --use-workspaces (CLI equivalent of your lerna.json config) does is call yarn install in the root. — Issue 1308

更多信息见github.com/lerna/lerna…

跨项目的本地开发

在我们的例子中,我们正在构建一个多包设计系统。如果开发人员想在设计系统中创建一个新的组件,但在发布之前也要在本地客户端应用程序中进行测试,他们可以通过使用yarn的链接命令来实现。

建立本地依赖关系的symlink

假设我们想在my-client-app中使用我们本地的my-design-system-core。 我们先cd到我们要在另一个项目中用到的软件包。

cd ~/path/to/my-design-system/my-design-system-core

然后我们创建一个symlink

yarn link

你应该看到这样的输出

success Registered "@my-scope-name/my-design-system-core".
info You can now run `yarn link "@my-scope/my-design-system-core"` in the projects where you want to use this module and it will be used instead.

现在我们的包已经有了符号链接,我们可以进入my-client-app中使用。

cd ~/path/to/my-client-app 
yarn link @my-scope-name/my-design-system-core

/packages/my-design-system-core 中的任何变化都会反映在my-client-app中。现在,开发人员可以很容易地在两个项目上进行本地开发,并看到它的反映。

解除本地依赖关系的链接

当开发者完成后,不再想使用本地的包时,我们需要解除链接。 cd到入我们要解除链接的包中

cd ~/path/to/my-design-system/my-design-system-core

运行unlink删除本地symlink

yarn unlink

你会看到这样的输出

success Unregistered "@my-scope-name/my-design-system-core".
info You can now run `yarn unlink "@my-scope-name/my-design-system-core"` in the projects where you no longer want to use this module.

现在,我们可以cd到my-client-app中解除链接。

cd ~/path/to/my-client-app
yarn unlink @my-scope-name/my-design-system-core

总结

Lerna与Yarn Workspaces是一个很好的组合。Lerna 在 Yarn Workspaces 的基础上增加了实用功能,用于处理多个包。纱线工作空间使得所有的依赖关系可以一起安装,使得缓存和安装速度更快。它让我们可以通过一个命令轻松地在NPM上发布依赖关系,当依赖关系的版本发生变化时,自动更新兄弟依赖关系的package.json,一般来说,安装、版本管理和发布都是一种无痛的体验。