结合 lerna 和 yarn workspace 管理多项目工作流

名词解释

  • 多个项目的代码放在在同一存储库中这种开发策略称之为 Monorepo
  • lerna Babel开发用来管理多包的工具,基于 Monorepo 理念在工具端的实现
  • yarn Facebook 贡献的 Javascript 包管理器
  • commitlint 用来规范git commit信息

背景

vue-json-schema-form 项目中,需要把 libdocdemo 放在同一个项目中管理,彼此独立,又可以相互依赖。

使用 yarn workspace 可以很好的解决上面的问题,搭配 lerna 做npm发布管理。目前大型仓库都使用这种方式,比如 Vue Vuepress 等。

由于 yarn workspacelerna 有较多的功能重叠,这里重叠的部分优先使用 workspace 。最后就是只有发布管理使用了 learn 其它使用 workspace

详细的可参考 https://github.com/lljj-x/vue-json-schema-form 配置。

创建项目

目录结构

1
2
3
4
5
6
├── packages
| ├── lib
| | ├── package.json
| ├── demo
| | ├── package.json
├── package.json

packages 下每个文件夹为一个单独完整的包

package.json配置

1
2
3
4
5
6
7
8
9
{
"private": true, // 禁止发布
"repository": "https://github.com/lljj-x/vue-json-schema-form",
"workspaces": [
"packages/lib",
"packages/demo",
// "packages/*" // 可以单个指定 也可直接配置 *
]
}

子package配置

子package即为每个独立的工作区。

如:packages/demo,使用 vue-cli

1
cd packages && vue create demo

demo/package.json 配置:

1
2
3
4
5
6
7
{
"private": true, // 禁止发布

"publishConfig": {
"access": "publish" // 如果该模块需要发布,对于scope模块,需要设置为publish,否则需要权限验证
}
}

yarn workspace

yarn install

安装所有依赖,包含子package依赖,如果子package之间存在相互依赖会通过创建软链的方式引用,而非npm下载,这里可能会影响webpack配置中使用 node_modules 做路径判断的地方。

1
yarn install

依赖树关系

1
yarn workspaces info

安装/删除依赖模块

单个package工作区

1
2
3
4
5
# packageA 安装 axios
yarn workspace packageA add axios

# packageA 移除 axios
yarn workspace packageA remove axios

packageA 是需要安装依赖的包名,即 package.json 中的 name 字段,而非目录名

root package

1
2
3
4
5
# root package 安装 commitizen
yarn add -W -D commitizen

# root package 移除 commitizen
yarn remove -W commitizen

运行单个 package 的scripts 命令

1
2
# 运行packageA 的dev命令
yarn workspace packageA dev
1
2
# 这里是在每个工作区运行 run build 命令
yarn workspaces run build

Tips:这里运行命令的时候不会检测依赖树关系,只是 package.json 文件 workspaces 配置工作区逐个运行,这里推荐使用 lerna build 见下文 lerna build

到这里对于不需要推送npm的情况下已经可以满足基本需要了。

lerna

目前使用lerna主要来做发布和版本管理

  • 全局安装
1
npm i -g lerna
  • 初始化一个项目
1
lerna init

包含两种工作模式:

  1. Fixed/Locked mode (default)
    固定模式,默认 packages下的所有包共用一个版本号(version),会自动将所有的包绑定到一个版本号上(该版本号也就是 lerna.json 中的 version 字段),所以任意一个包发生了更新,这个共用的版本号就会发生改变。

  2. Independent mode
    独立模式,允许每一个包有一个独立的版本号,在使用 lerna publish 命令时,可以为每个包单独制定具体的操作,同时可以只更新某一个包的版本号。

    lerna.json 中的 version 字段指定为 independent 即可,或者 lerna init --independent 命令初始化

lerna.json 文件配置大致如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"npmClient": "yarn",
"useWorkspaces": true, // 使用yarn workspaces
"version": "0.0.1", // 当前版本 或者 independent 独立模式
"command": {
"version": {
"allowBranch": "master",
"exact": true,
"ignoreChanges": [
"**/*.md"
],
"message": "build: release version %v"
}
}
}

lerna clean

清除所用的 node_modules 目录

1
lerna clean

lerna diff

显示修改内容 类似git diff

1
lerna diff

lerna ls

列出所有的子package

1
lerna ls -l

lerna changed

列出修改过的子package

1
lerna changed

lerna build

build 所有子package,​子package分别执行 build--sort ​参数可以控制以拓扑排序规则执行命令

1
lerna run --stream --sort build

lerna version

lerna version 的作用是进行 version bump,支持手动和自动两种模式

手动确定新版本

1
2
# 按着提示选择版本即可
lerna version

自动确定版本

自动根据 conventional commit 规范确定版本

存在feat提交: 需要更新minor版本
存在fix提交: 需要更新patch版本
存在BREAKING CHANGE提交: 需要更新大版本

1
2
3
4
5
# 生成changelog文件以及根据commit来进行版本变动
lerna version --conventional-commits

# 生成changelog文件以及根据commit来进行版本变动,不提示用户输入版本
lerna version --conventional-commits --yes

可参见官方文档 lerna version

version 成功后会自动推送当前分支,可以结合配置 lerna.json文件 commandversion字段 配置允许version的分支,commit 信息等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.0.1",
"command": {
"version": {
"allowBranch": "master",
"exact": true,
"ignoreChanges": [
"**/*.md"
],
"message": "build: release version %v"
}
}
}

lerna publish

lerna publish 的功能可以即包含version的工作,也可以单纯的只做发布操作。

可参见官方文档 lerna publish

1
lerna publish

lerna publish 会先调用 lerna version,再确定是否要发布到npm

1
lerna publish from-git

from-git 基于当前git提交的软件包做发布,一般都是通过 lerna version 提交的版本

1
lerna publish from-package

from-package 在注册表中不存在该版本的最新提交中发布程序包 (这个我没用过)

lerna 不会发布标记为私有的软件包(package.json"private": true

changelog

根据 conventional commit 提交规范,即可通过工具为每个 package 生成 changelog 文件。

conventional commit 支持

conventional commit规范使用也可以看这个:Commit message 和 Change log 编写指南

安装如下依赖:

1
yarn add -W -D commitizen cz-conventional-changelog @commitlint/cli @commitlint/config-conventional husky conventional-changelog-cli

commitizen:

一个撰写合格 Commit message 的工具

cz-conventional-changelog:

用于使 commitizen 支持Angular的Commit message格式

配置 package.json 添加如下配置

1
2
3
4
5
6
7
{
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

至此就可以通过 git cz 命令替换 git commit 生成符合格式的Commit message。

git cz 命令出现如下选项

git-cz

@commitlint/cli:
commitlint 用于检查您的提交消息是否符合提交格式,类似 eslint 校验js语法

@commitlint/config-conventional:
commitlint 校验规则,类似 eslint-config-standard

添加并配置 .commitlintrc.js 文件

1
2
3
4
5
6
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
...otherRules // 可以继续配置你的规则
}
};

husky:
husky是 Git hooks 工具,这里用于在 commit 时校验message内容

配置 package.json 文件,添加如下

1
2
3
4
5
6
7
{
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}

至此当新提交的commit message 不符合规范便会阻止提交。

conventional-changelog-cli:
根据commit message生成 changelog.md 文件 (这里功能和 lerna version --conventional-commits 生成changelog 有部分重叠,下文会详细区分)。

如:conventional-changelog -p angular -i CHANGELOG.md -s -r 2

注意:这里生成的是整个仓库的 changelog ,而非每个 package生成 changelog

生成changelog

生成changelog 依赖如上 conventional-commit 规范message

整个仓库 changelog

生成自从上次发布以来的变动:

1
conventional-changelog -p angular -i CHANGELOG.md -w

如果这是您第一次使用此工具,并且想要生成所有以前的变更日志,则可以执行:

1
conventional-changelog -p angular -i CHANGELOG.md -s -r 0

参见:conventional-changelog-cli

每个package工作区独立 changelog

lerna version 时,自动模式 --conventional-commits 命令会同时为每个package工作区生成 changelog

1
lerna version --conventional-commits

注: lerna version 成功之后便会为每个 package 生成 changelog,包括 root package

npm scripts

如下:自己常配的一些script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"scripts": {
"demo:dev": "yarn workspace demo dev",
"demo:build": "yarn workspace demo build",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 2",
"clean": "lerna clean && rm -rf node_modules",
"packages:diff": "lerna diff",
"packages:list": "lerna ls -l",
"packages:changed": "lerna changed",
"packages:build": "lerna run --stream --sort build",
"publish": "lerna publish",
"autoPublish": "lerna publish --conventional-commits --yes",
"version": "lerna version --conventional-commits --yes"
}
}
  • changelog 生成整个仓库 changelog
  • publish 手动选择版本并发包 (其实我自己平时一般用这个)
  • autoPublish 自动确定版本并发包同时生成每个package changelog
  • version 自动确定版本不发布和生成每个package changelog

参考:https://zhuanlan.zhihu.com/p/71385053