Open astonishqft opened 2 years ago
lerna最佳实践
在介绍我们今天的主角 lerna 之前,首先了解下什么是 multirepo ?什么是 monorepo ?
multirepo
monorepo
multirepo 指的是将模块分为多个仓库,monorepo 指的是将多个模块放在一个仓库中。
multirepo 可以让每个团队都拥有自己的仓库,他们可以使用自己的构建流程、代码规范等,但是同时也会存在很多问题,比如模块之间如果存在相互依赖,就必须到目标仓库里进行bug修复、构建、发版本等,相互依赖关系越复杂,处理起来就越困难。
monorepo 可以让多个模块共享同一个仓库,因此他们可以共享同一套构建流程、代码规范也可以做到统一,特别是如果存在模块间的相互依赖的情况,查看代码、修改bug、调试等会更加方便,因此也越来越受到大家的关注,像 Babel、React、Vue 等主流的开源仓库都采用的 monorepo。
Babel
React
Vue
lerna 是一个管理工具,用于管理包含多个软件包(package)的 JavaScript 项目,最早是 Babel 自己用来维护自己的 monorepo 并开源出的一个项目,针对使用 git 和 npm 管理多软件包代码仓库的工作流程进行优化,解决多个包互相依赖,且发布需要手动维护多个包的问题。
git
npm
总结一下,使用 lerna 可以帮我们解决如下几个痛点:
lerna
多个仓库之间可以管理管理公共的依赖包,或者单独管理各自的依赖包
方便模块之间的相互引用,模块之间的调试不必发版本,lerna内部会自动进行link
lerna提供了两种模式,支持选择单独针对某个包发版本或者统一发版本
多个仓库之间可以共享统一的代码规范,版本管理更加规范
以下我会分两个部分介绍下 lerna,首先是介绍 lerna 的常规用法,然后介绍下 lerna 的最佳实践。
$ npm install --global lerna
$ git init lerna-repo && cd lerna-repo
$ lerna init
注意,lerna init 可以通过参数 --independent 进入 Independent 模式,该模式可以单独发版本。
lerna init
--independent
Independent
初始化之后的工程目录结构如下:
lerna-repo ├── lerna.json ├── package.json └── packages
lerna.json:
{ "packages": [ "packages/*" ], "version": "0.0.0" }
package.json:
{ "name": "root", "private": true, "devDependencies": { "lerna": "^4.0.0" } }
使用 lerna create 创建两个包 pkg1 和 pkg2
$ lerna create pkg1 $ lerna create pkg2
创建完成后的目录结构如下:
lerna-demo ├── README.md ├── lerna.json ├── package.json └── packages ├── pkg1 │ ├── README.md │ ├── __tests__ │ ├── lib │ └── package.json └── pkg2 ├── README.md ├── __tests__ ├── lib └── package.json
给 pkg1 和 pkg2 这两个包都安装 fs-extra 这个包,pkg1 和 pkg2 的 package.json 的 dependency 会同时包含 fs-extra 这个包。
pkg1
pkg2
fs-extra
package.json
dependency
$ lerna add fs-extra
安装 fs-extra 之后的目录结构:
lerna-demo ├── README.md ├── lerna.json ├── package.json └── packages ├── pkg1 │ ├── README.md │ ├── __tests__ │ ├── lib │ ├── node_modules │ ├── package-lock.json │ └── package.json └── pkg2 ├── README.md ├── __tests__ ├── lib ├── node_modules ├── package-lock.json └── package.json
比如给 pkg1 安装一个 glob 包,给 pkg2 安装一个 ora 包:
glob
ora
$ lerna add glob --scope pkg1 $ lerna add ora --scope pkg2
其中 --scope 参数用来指定具体给哪个 package 安装包,注意 --scope 后面的参数是对应模块的 package.json 中的 name 字段名。
--scope
package
name
将 pkg1 作为 pkg2 的依赖进行安装:
$ lerna add pkg1 --scope pkg2
需要注意的是,通过这种方式安装的依赖,并不会将 pkg1 安装到 pkg2 的 node_modules 里,而是通过 symlink 的形式进行关联。
node_modules
symlink
以上包确认没有问题之后,就可以通过执行 lerna publish 进行发布了。
lerna publish
在进行 publish 之前需要首先提交你的代码,否则 lerna 会报错:
publish
lerna ERR! ENOCOMMIT No commits in this repository. Please commit something before using version.
提交代码并关联到 git 仓库:
$ git add . $ git commit -m 'init' $ git remote add origin git@github.com:astonishqft/lerna-demo.git // 关联到远程git仓库 $ git push -u origin main
将 pkg1 里面的 glob 包删除:
$ lerna exec --scope=pkg1 npm uninstall glob
上面可以看到,pkg1 和 pkg2 都依赖了 fs-extra 这个包,而各自 package 下面的 node_modules 都进行了一次安装,因此我们可以通过 --hoist 来抽取重复的依赖到最外层的 node_modules 目录下,同时最外层的 package.josn 的依赖信息也不会进行更新。
--hoist
package.josn
$ lerna bootstrap --hoist
但是这种方式会有一个问题,不同版本号只会保留使用最多的版本,这种配置不太好,当项目中有些功能需要依赖老版本时,就会出现问题,因此这种方式不推荐使用。
前面我们已经介绍了 lerna 的相关概念和基本用法,目前最常见的解决方案是基于 lerna 和 yarn workspace 的 monorepo 工作流。
yarn workspace
由于 yarn 和 lerna 在功能上有较多的重叠,我们采用 yarn 官方推荐的做法:
yarn
用 yarn 来处理依赖问题,用 lerna 来处理发布问题。
yarn workspaces 是 yarn 提供的 monorepo 的依赖管理机制,用于在代码仓库的根目录下管理多个 package 依赖,与 lerna 不同的是,yarn workspaces 可以解决前面说的当不同的 package 依赖不同的版本号问题,yarn workspaces 会检查每个子项目里面依赖及其版本,如果版本不一致都会安装到各自 package 的 node_modules 中,只有依赖版本号一致的时候才会提升到顶层,而 lerna 会进到每个 package 中执行 yarn/npm install,因此会在每个 package 下生成一个 node_modules。
yarn workspaces
yarn/npm install
yarn workspaces 首先在工程的根目录下的 package.json 中增加 "private": true 和 "workspaces”: [ "packages/*"] 配置项。"private": true 可以确保根目录不会被发布出去,"workspaces”: [ "packages/*"] 声明了 workspaces 中所包含的项目路径。
"private": true
"workspaces”: [ "packages/*"]
workspaces
package.json 配置文件增加如下配置。
{ "private": true, "workspaces": [ "packages/*" ], }
lerna.json 配置文件增加如下配置。
lerna.json
{ "useWorkspaces": true, "npmClient": "yarn", }
对于一个已经存在的 monorepo 仓库,使用 yarn install 安装依赖。yarn install 会自动安装依赖并且解决同一个仓库之间多个package 之间的 link 问题。
yarn install
link
yarn install 等价于 lerna bootstrap --npm-client yarn --use-workspace。
lerna bootstrap --npm-client yarn --use-workspace
使用 lerna clean 可以清理每个 package 下的 node_modules,但是没有办法清理根目录下的 node_modules 目录,因此,我们可以在根目录下的 package.json 目录的 scripts 中增加一条 clean 命令,用于清理环境。
lerna clean
scripts
clean
{ "clear-all": "rimraf node_modules && lerna clean -y" }
安装依赖一般分为三种情况:
安装到workspace-root
对于一些打包工具或者代码规范校验工具,可以使用 yarn -W add [package] [--dev] 进行安装,比如 typescript、eslint、cross-env、babel、rollup等。这类包一般都是一些开发依赖,比如将 ts 代码转换成 es5 代码或者一些代码校验工具等。通过这种方式安装的依赖包是装在根目录下的 node_modules 中。
yarn -W add [package] [--dev]
ts
es5
给所有的 package 都安装依赖
比如如果想给每个 package 都安装一个 lodash 包,就可以使用 yarn workspace add lodash 给每个 package 都安装 lodash。
lodash
yarn workspace add lodash
给指定的某个 package 安装依赖
通过 yarn workspace pkgA add pkgB 可以将 pkgB 作为依赖安装到 pkgA 中,需要注意的是,如果是 packages 之间的相互安装,安装的时候可以指定到具体的版本号,否则安装的时候会去npm上搜索,但是因为某个包还没有发包出去,导致安装失败。
yarn workspace pkgA add pkgB
pkgB
pkgA
删除包的时候只需要把上述 add 换成 remove 即可。
add
remove
通过运行 yarn workspace <workspace_name> <command> 命令运行某个执行 package 下的某个 script 命令。
yarn workspace <workspace_name> <command>
script
比如执行 pkgA 下的 build 命令,可以运行 yarn workspace pkgA run build。如果想运行 package 下的 build 命令,可以运行 yarn workspaces run build。
build
yarn workspace pkgA run build
yarn workspaces run build
代码编写完毕后接下来就涉及到代码的提交,为了规范代码提交格式,方便自动生成 changelog,这里需要借助以下几个工具。
commitizen 的作用主要是为了生成标准化的 commit message,符合 Angular规范。
commit message
一个标准化的 commit message 应该包含三个部分:Header、Body 和 Footer,其中的 Header 是必须的,Body 和 Footer 可以选填。
Header
Body
Footer
<type>(<scope>): <subject> // 空一行 <body> // 空一行 <footer>
Header 部分由三个字段组成:type(必需)、scope(可选)、subject(必需)
type
scope
subject
Type
type 必须是下面的其中之一:
用于说明本次提交的影响范围。scope 依据项目而定,例如在业务项目中可以依据菜单或者功能模块划分,如果是组件库开发,则可以依据组件划分。
主题包含对更改的简洁描述:
注意三点:
主要包含对主题的进一步描述,同样的,应该使用祈使语气,包含本次修改的动机并将其与之前的行为进行对比。
包含此次提交有关重大更改的信息,引用此次提交关闭的issue地址,如果代码的提交是不兼容变更或关闭缺陷,则Footer必需,否则可以省略。
使用方法:
安装 commitizen,如果需要在项目中使用 commitizen 生成符合 AngularJS 规范的提交说明,还需要安装 cz-conventional-changelog 适配器:
commitizen
AngularJS
cz-conventional-changelog
$ yarn -W add commitizen cz-conventional-changelog -D
package.json 中增加一条 script: commit: "git-cz",并且在 config 字段中指定 cz-conventional-changelog 路径:
commit: "git-cz"
config
{ "name": "root", "private": true, "workspaces": [ "packages/*" ], "scripts": { "commit": "git-cz" }, "config": { "commitizen": { "path": "cz-conventional-changelog" } }, "devDependencies": { "commitizen": "^4.2.4", "cz-conventional-changelog": "^3.3.0", "lerna": "^4.0.0" } }
接下来就可以使用 yarn commit 来代替 git commit 进行代码提交了。
yarn commit
git commit
前面我们提到,通过 commitizen && z-conventional-changelog 可以规范我们的 commit message,但是同时也存在一个问题,如果用户不通过 yarn commit 来提交代码,而是直接通过 git commit 命令来提交代码,就能绕开 commit message 检查,这是我们不希望看到的。
z-conventional-changelog
因此接下来我们使用 commitlint 结合 husky 来对我们的提交行为进行约束。在 git commit 提交之前使用 git 钩子来验证信息。提交不符合规则的信息将会被阻止提交。
commitlint
husky
安装 commitlint 和 husky:
$ yarn -W add @commitlint/cli @commitlint/config-conventional husky -D
在工程根目录下增加 commitlint.config.js 配置文件,指定 commitlint 的校验配置文件:
commitlint.config.js
commitlint.config.js:
已经废弃的配置:
module.exports = { extends: ['@commitlint/config-conventional'] }
需要注意的是,如果安装的 husky 的版本是 7.x 的,以往直接在 package.json 中 hooks 字段增加的配置项已经被废弃了,具体可以参考这里
7.x
hooks
"husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } },
下面在 husky 7.x 版本下进行 commit-msg 的配置。
husky 7.x
commit-msg
npm husky install
或者如果想在安装后自动启动 husky,在 package.json 的scripts中增加一条script命令:
"scripts": { "prepare": "husky install" },
prepare 是 NPM 操作生命周期中的一环,在执行 install 的时候会按生命周期顺序执行相应钩子:preinstall -> install -> postinstall -> prepublish -> preprepare -> prepare -> postprepare
prepare
NPM
install
preinstall -> install -> postinstall -> prepublish -> preprepare -> prepare -> postprepare
执行完毕后就会在根目录下创建一个 .husky 目录。
.husky
husky add <file> [cmd]
hook
执行 npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"' 会在 .husky 下生成一个 commit-msg 的 shell 文件:
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
shell
#!/bin/sh . "$(dirname "$0")/_/husky.sh" npx --no-install commitlint --edit "$1"
这样,当我们在执行 git commit -m 'xxx' 的时候,如果提交的 commit message 不符合规范就会报如下的错误,只有提交信息符合提交规范才会允许代码提交。
git commit -m 'xxx'
配置 eslint 对代码进行统一的规范校验,配合 lint-staged 可以对已经提交的代码进行校验。
eslint
lint-staged
安装 eslint 和 lint-stage:
lint-stage
$ yarn -W add eslint lint-staged @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
增加 .eslintrc.js 配置文件:
.eslintrc.js
module.exports = { 'parser': '@typescript-eslint/parser', 'plugins': ['@typescript-eslint'], 'rules': { 'no-var': 'error',// 不能使用var声明变量 'no-extra-semi': 'error', '@typescript-eslint/indent': ['error', 2], 'import/extensions': 'off', 'linebreak-style': [0, 'error', 'windows'], 'indent': ['error', 2, { SwitchCase: 1 }], // error类型,缩进2个空格 'space-before-function-paren': 0, // 在函数左括号的前面是否有空格 'eol-last': 0, // 不检测新文件末尾是否有空行 'semi': ['error', 'always'], // 在语句后面加分号 'quotes': ['error', 'single'],// 字符串使用单双引号,double,single 'no-console': ['error', { allow: ['log', 'warn'] }],// 允许使用console.log() 'arrow-parens': 0, 'no-new': 0,//允许使用 new 关键字 'comma-dangle': [2, 'never'], // 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号,always-multiline多行模式必须带逗号,单行模式不能带逗号 'no-undef': 0 }, 'parserOptions': { 'ecmaVersion': 6, 'sourceType': 'module', 'ecmaFeatures': { 'modules': true } } };
lint-staged staged 是 Git 里的概念,表示暂存区,lint-staged 表示只检查暂存区中的文件。
lint-staged staged
Git
package.json 中增加如下配置:
"lint-staged": { "*.ts": [ "eslint --fix", "git add" ] }
husky 中增加 pre-commit 校验:
pre-commit
$ npx husky add .husky/pre-commit "npx --no-install lint-staged"
通过上面的方式,严格 commit message 的提交规范,就可以方便的通过 lerna 完成生成 changelog、打 git tag、 更新 package.json 的 version 版本号、发布到 npm 等操作。
changelog
git tag
version
lerna publish 的时候会做以下操作:
找出从上一个版本发布以来有过变更的 package
提示开发者确定要发布的版本号
将所有更新过的的 package 中的 package.json 的 version 字段更新
将依赖更新过的 package 的 包中的依赖版本号更新
更新 lerna.json 中的 version 字段
提交上述修改,并打一个 tag
tag
推送到 git 仓库
changelog 的生成可以通过 lerna version --conventional-commits 自动生成,关于 --conventional-commits 参数,lerna 是这么描述的:
lerna version --conventional-commits
--conventional-commits
Use conventional-changelog to determine version bump and generate CHANGELOG.
在 lerna.json 增加如下配置:
{ "command": { "version": { "conventionalCommits": true } }, }
lerna version 会检测从上一个版本发布以来的变动,但有一些文件的提交,我们不希望触发版本的变动,譬如 .md 文件的修改,并没有实际引起 package 逻辑的变化,不应该触发版本的变更。可以通过 ignoreChanges 配置排除。
lerna version
.md
ignoreChanges
{ "ignoreChanges": [ "**/*.md" ], }
配置好之后,通过 lerna publish 完成changelog生成、版本号的修改和npm发包等操作了。
以上就是一个完整的基于 lerna + yarn workspace 的 monorepo 的实践流程,里面包含了依赖包的管理、完善的工作流、统一的代码风格、一键发布机制等,当然还有一些不够完善的地方需要自己补充,比如单元测试等,有需要的可以基于我的例子进行补充完善。
lerna + yarn workspace
本示例github地址。
更多精彩内容欢迎关注我的公众号!前端架构师笔记
前端架构师笔记
test
555
lerna最佳实践
multirepo VS monorepo
在介绍我们今天的主角 lerna 之前,首先了解下什么是
multirepo
?什么是monorepo
?multirepo
指的是将模块分为多个仓库,monorepo
指的是将多个模块放在一个仓库中。multirepo
可以让每个团队都拥有自己的仓库,他们可以使用自己的构建流程、代码规范等,但是同时也会存在很多问题,比如模块之间如果存在相互依赖,就必须到目标仓库里进行bug修复、构建、发版本等,相互依赖关系越复杂,处理起来就越困难。monorepo
可以让多个模块共享同一个仓库,因此他们可以共享同一套构建流程、代码规范也可以做到统一,特别是如果存在模块间的相互依赖的情况,查看代码、修改bug、调试等会更加方便,因此也越来越受到大家的关注,像Babel
、React
、Vue
等主流的开源仓库都采用的monorepo
。lerna
lerna 是一个管理工具,用于管理包含多个软件包(package)的 JavaScript 项目,最早是
Babel
自己用来维护自己的monorepo
并开源出的一个项目,针对使用git
和npm
管理多软件包代码仓库的工作流程进行优化,解决多个包互相依赖,且发布需要手动维护多个包的问题。总结一下,使用
lerna
可以帮我们解决如下几个痛点:多个仓库之间可以管理管理公共的依赖包,或者单独管理各自的依赖包
方便模块之间的相互引用,模块之间的调试不必发版本,lerna内部会自动进行link
lerna提供了两种模式,支持选择单独针对某个包发版本或者统一发版本
多个仓库之间可以共享统一的代码规范,版本管理更加规范
以下我会分两个部分介绍下
lerna
,首先是介绍lerna
的常规用法,然后介绍下lerna
的最佳实践。基本用法
安装
创建一个git仓库
初始化一个 lerna 仓库
注意,
lerna init
可以通过参数--independent
进入Independent
模式,该模式可以单独发版本。初始化之后的工程目录结构如下:
lerna.json:
package.json:
新增package包
使用 lerna create 创建两个包 pkg1 和 pkg2
创建完成后的目录结构如下:
给两个package增加公共依赖
给
pkg1
和pkg2
这两个包都安装fs-extra
这个包,pkg1
和pkg2
的package.json
的dependency
会同时包含fs-extra
这个包。安装
fs-extra
之后的目录结构:给某个包单独安装指定依赖
比如给
pkg1
安装一个glob
包,给pkg2
安装一个ora
包:其中
--scope
参数用来指定具体给哪个package
安装包,注意--scope
后面的参数是对应模块的package.json
中的name
字段名。添加内部模块之间的依赖
将
pkg1
作为pkg2
的依赖进行安装:需要注意的是,通过这种方式安装的依赖,并不会将
pkg1
安装到pkg2
的node_modules
里,而是通过symlink
的形式进行关联。发布
以上包确认没有问题之后,就可以通过执行
lerna publish
进行发布了。在进行
publish
之前需要首先提交你的代码,否则lerna
会报错:提交代码并关联到
git
仓库:删除某个包
将
pkg1
里面的glob
包删除:抽离公共的包
上面可以看到,
pkg1
和pkg2
都依赖了fs-extra
这个包,而各自package
下面的node_modules
都进行了一次安装,因此我们可以通过--hoist
来抽取重复的依赖到最外层的node_modules
目录下,同时最外层的package.josn
的依赖信息也不会进行更新。但是这种方式会有一个问题,不同版本号只会保留使用最多的版本,这种配置不太好,当项目中有些功能需要依赖老版本时,就会出现问题,因此这种方式不推荐使用。
最佳实践
前面我们已经介绍了
lerna
的相关概念和基本用法,目前最常见的解决方案是基于lerna
和yarn workspace
的monorepo
工作流。由于
yarn
和lerna
在功能上有较多的重叠,我们采用yarn
官方推荐的做法:用
yarn
来处理依赖问题,用lerna
来处理发布问题。yarn workspaces 与 lerna
yarn workspaces
是yarn
提供的monorepo
的依赖管理机制,用于在代码仓库的根目录下管理多个package
依赖,与lerna
不同的是,yarn workspaces
可以解决前面说的当不同的package
依赖不同的版本号问题,yarn workspaces
会检查每个子项目里面依赖及其版本,如果版本不一致都会安装到各自package
的node_modules
中,只有依赖版本号一致的时候才会提升到顶层,而lerna
会进到每个package
中执行yarn/npm install
,因此会在每个package
下生成一个node_modules
。yarn workspaces
首先在工程的根目录下的package.json
中增加"private": true
和"workspaces”: [ "packages/*"]
配置项。"private": true
可以确保根目录不会被发布出去,"workspaces”: [ "packages/*"]
声明了workspaces
中所包含的项目路径。package.json
配置文件增加如下配置。开启yarn workspaces
lerna.json
配置文件增加如下配置。工程初始化
对于一个已经存在的
monorepo
仓库,使用yarn install
安装依赖。yarn install
会自动安装依赖并且解决同一个仓库之间多个package
之间的link
问题。yarn install
等价于lerna bootstrap --npm-client yarn --use-workspace
。清理环境
使用
lerna clean
可以清理每个package
下的node_modules
,但是没有办法清理根目录下的node_modules
目录,因此,我们可以在根目录下的package.json
目录的scripts
中增加一条clean
命令,用于清理环境。package.json:
安装依赖
安装依赖一般分为三种情况:
安装到workspace-root
对于一些打包工具或者代码规范校验工具,可以使用
yarn -W add [package] [--dev]
进行安装,比如 typescript、eslint、cross-env、babel、rollup等。这类包一般都是一些开发依赖,比如将ts
代码转换成es5
代码或者一些代码校验工具等。通过这种方式安装的依赖包是装在根目录下的node_modules
中。给所有的
package
都安装依赖比如如果想给每个
package
都安装一个lodash
包,就可以使用yarn workspace add lodash
给每个package
都安装lodash
。给指定的某个 package 安装依赖
通过
yarn workspace pkgA add pkgB
可以将pkgB
作为依赖安装到pkgA
中,需要注意的是,如果是 packages 之间的相互安装,安装的时候可以指定到具体的版本号,否则安装的时候会去npm上搜索,但是因为某个包还没有发包出去,导致安装失败。删除包的时候只需要把上述
add
换成remove
即可。运行workspace的command
通过运行
yarn workspace <workspace_name> <command>
命令运行某个执行package
下的某个script
命令。比如执行
pkgA
下的build
命令,可以运行yarn workspace pkgA run build
。如果想运行package
下的build
命令,可以运行yarn workspaces run build
。代码提交
代码编写完毕后接下来就涉及到代码的提交,为了规范代码提交格式,方便自动生成 changelog,这里需要借助以下几个工具。
commitizen && cz-conventional-changelog
commitizen 的作用主要是为了生成标准化的
commit message
,符合 Angular规范。一个标准化的
commit message
应该包含三个部分:Header
、Body
和Footer
,其中的Header
是必须的,Body
和Footer
可以选填。Header
部分由三个字段组成:type
(必需)、scope
(可选)、subject
(必需)Type
type
必须是下面的其中之一:scope
用于说明本次提交的影响范围。
scope
依据项目而定,例如在业务项目中可以依据菜单或者功能模块划分,如果是组件库开发,则可以依据组件划分。subject
主题包含对更改的简洁描述:
注意三点:
Body
主要包含对主题的进一步描述,同样的,应该使用祈使语气,包含本次修改的动机并将其与之前的行为进行对比。
Footer
包含此次提交有关重大更改的信息,引用此次提交关闭的issue地址,如果代码的提交是不兼容变更或关闭缺陷,则Footer必需,否则可以省略。
使用方法:
安装
commitizen
,如果需要在项目中使用commitizen
生成符合AngularJS
规范的提交说明,还需要安装cz-conventional-changelog
适配器:package.json
中增加一条 script:commit: "git-cz"
,并且在config
字段中指定cz-conventional-changelog
路径:接下来就可以使用
yarn commit
来代替git commit
进行代码提交了。commitlint && husky
前面我们提到,通过
commitizen
&&z-conventional-changelog
可以规范我们的commit message
,但是同时也存在一个问题,如果用户不通过yarn commit
来提交代码,而是直接通过git commit
命令来提交代码,就能绕开commit message
检查,这是我们不希望看到的。因此接下来我们使用
commitlint
结合husky
来对我们的提交行为进行约束。在git commit
提交之前使用git
钩子来验证信息。提交不符合规则的信息将会被阻止提交。安装
commitlint
和husky
:在工程根目录下增加
commitlint.config.js
配置文件,指定commitlint
的校验配置文件:commitlint.config.js:
已经废弃的配置:
需要注意的是,如果安装的
husky
的版本是7.x
的,以往直接在package.json
中hooks
字段增加的配置项已经被废弃了,具体可以参考这里下面在
husky 7.x
版本下进行commit-msg
的配置。npm husky install
或者如果想在安装后自动启动 husky,在 package.json 的scripts中增加一条script命令:
prepare
是NPM
操作生命周期中的一环,在执行install
的时候会按生命周期顺序执行相应钩子:preinstall -> install -> postinstall -> prepublish -> preprepare -> prepare -> postprepare
执行完毕后就会在根目录下创建一个
.husky
目录。husky add <file> [cmd]
指令来添加一条hook
执行
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
会在.husky
下生成一个commit-msg
的shell
文件:这样,当我们在执行
git commit -m 'xxx'
的时候,如果提交的commit message
不符合规范就会报如下的错误,只有提交信息符合提交规范才会允许代码提交。eslint 配置
配置
eslint
对代码进行统一的规范校验,配合lint-staged
可以对已经提交的代码进行校验。安装
eslint
和lint-stage
:增加
.eslintrc.js
配置文件:lint-staged staged
是Git
里的概念,表示暂存区,lint-staged
表示只检查暂存区中的文件。package.json
中增加如下配置:husky
中增加pre-commit
校验:版本发布
通过上面的方式,严格
commit message
的提交规范,就可以方便的通过lerna
完成生成changelog
、打git tag
、 更新package.json
的version
版本号、发布到npm
等操作。lerna puplish
lerna publish
的时候会做以下操作:找出从上一个版本发布以来有过变更的
package
提示开发者确定要发布的版本号
将所有更新过的的
package
中的package.json
的version
字段更新将依赖更新过的
package
的 包中的依赖版本号更新更新
lerna.json
中的version
字段提交上述修改,并打一个
tag
推送到
git
仓库changelog
的生成可以通过lerna version --conventional-commits
自动生成,关于--conventional-commits
参数,lerna
是这么描述的:在
lerna.json
增加如下配置:lerna version
会检测从上一个版本发布以来的变动,但有一些文件的提交,我们不希望触发版本的变动,譬如.md
文件的修改,并没有实际引起package
逻辑的变化,不应该触发版本的变更。可以通过ignoreChanges
配置排除。配置好之后,通过
lerna publish
完成changelog生成、版本号的修改和npm发包等操作了。总结
以上就是一个完整的基于
lerna + yarn workspace
的monorepo
的实践流程,里面包含了依赖包的管理、完善的工作流、统一的代码风格、一键发布机制等,当然还有一些不够完善的地方需要自己补充,比如单元测试等,有需要的可以基于我的例子进行补充完善。本示例github地址。
参考链接:
更多精彩内容欢迎关注我的公众号!![微信二维码图片](https://user-images.githubusercontent.com/15138753/156953473-c49e77cd-b4bc-4a20-a808-79ff3837c9af.png)
前端架构师笔记