Lerna初探
lerna是一个用于管理包含多个package结构的代码仓库的工具,优化工作流。新版的vue-cli、nuxt与babel均使用lerna进行自身的package管理。
package可理解为功能模块或子项目。
本文使用的lerna版本: version 3.8.0
场景
- 当存在一个含有多个package的monorepo
- 管理这些package的版本与发布时
- 管理package共用的代码规范等配置时
- 管理package共用的依赖时
介绍
lerna的主要功能可以分为:版本控制
与发布
,需要与npm(或yarn)和git一同使用。
模式
-
fixed/locked(默认)
固定模式。该模式为单版本号,在根目录中的
lerna.json
中设置。当使用lerna publish
时,如果自从上次发布后有模块改动,那么将会更新到新发布的版本。这也是目前Babel用的模式,当你想要自动整合不同包的版本时使用这个模式。它的特点是任何package的major change均会导致所有包都会进行major version的更新。
-
independent
lerna init --independent
独立模式。该模式中允许开发者独立管理多个包的版本更新。每次发布时,会得到针对每个包改动(patch, minor, major custom change)的提示。lerna会配合git,检查文件变动,只发布有改动的package。
独立模式允许开发者更新指定package的版本。将
lerna.json
中的version
键设为independent
来启用独立模式。
配置
在项目根目录的lerna.json
中设置lerna的相关配置。
{
"version": "1.0.0",
"npmClient": "yarn",
"command": {
"publish": {
"ignoreChanges": ["ignored-file", "*.md"]
}
},
"packages": [
"packages/*"
]
}
常用的字段:
key | value |
---|---|
version |
当前仓库版本,当设为independent 时开启独立模式 |
npmClient |
执行命令的client,默认为npm ,可以设为yarn |
command.publish.ignoreChanges |
设置不会包含进lerna change/publish 操作的文件路径,使用它来避免一些非重要改动时的版本更新,比如更新README.md 中的拼写错误 |
packages |
用于定位package的文件路径 |
yarn workspace
lerna与yarn的workspace
特性很好的融合在了一起,前者负责版本管理与发布
,后者负责依赖管理
。
workspace
的特点:在所有workspaces所匹配的项目路径下会执行统一的yarn命令,包含测试、安装依赖或执行脚本。
在lerna中启用workspace:
lerna.json
中lerna的设置
{
...
"npmClient": "yarn",
"useWorkspaces": true,
...
}
lerna与yarn workspace
有很好的相性,设置useWorkspaces
等价于使用bootstrap
命令的--use-workspaces
选项,详情见bootstrap
根目录下的package.json
{
...
"private": true,
"workspaces": [
"packages/*"
],
...
}
"private": true
是必须的,workspaces
为工作空间中所包含的项目路径,详见workspace
注意事项
需要注意的是,若开启了workspace功能,则lerna会将package.json
中workspaces
中所设置的项目路径作为lerna packages
的路径,而不会使用lerna.json
中的packages
值。相关源码:
get packageConfigs() {
if (this.config.useWorkspaces) {
const workspaces = this.manifest.get("workspaces");
...
return workspaces.packages || workspaces;
}
return this.config.packages || [Project.PACKAGE_GLOB];
}
也就是说,如果使用workspace
时未开启useWorkspaces
,则yarn
与lerna
会分别管理对应的项目路径。
以vue-cli为例,它的lerna.json
配置:
{
"npmClient": "yarn",
"useWorkspaces": false,
"version": "3.2.1",
"packages": [
"packages/@vue/babel-preset-app",
"packages/@vue/cli*"
]
}
根目录下的package.json
:
{
"private": true,
"workspaces": [
"packages/@vue/*",
"packages/test/*",
"packages/vue-cli-version-marker"
],
...
}
它将useWorkspaces
设为了false,那么意味着使用yarn
管理的是package.json
中workspaces
所对应的项目路径下的依赖,有@vue
下的所有项目,test
中的测试文件和vue-cli-version-marker
。而leran
管理的是lerna.json
中packages
所对应的@vue/babel-preset-app
和@vue/cli*
的版本与发布。
而在nuxt
中则是lerna
与yarn workspace
均采用了相同的package路径。
依赖管理与npm script
下面所操作的lerna
项目默认开启了useWorkspaces
初始化
安装lerna与初始化lerna项目
```shell
yarn global add lerna
mkdir monorepo && cd monorepo
lerna init
```
创建package
```shell
cd packages
mkdir module-a && cd module-a
yarn init
```
将package的`name`设置成统一的`@repo/module`的格式,在这里就是`@monorepo/module-a`
依赖的安装与移除
添加所有package中的依赖
lerna add dep-name
会将dep-name
包安装到packages所包含的package中。
移除所有package中的依赖
lerna exec -- yarn remove dep-name
移除packages所包含的package中的dep-name
包。
给指定package中添加依赖
lerna add dep-name --scope module-a
在module-a
package中添加的dep-name
包,使用--scope
命令限定目标package范围。
移除指定package中的依赖
lerna目前并没有remove
这种命令,需要在对应package的package.json
中删除对应依赖,然后执行lerna bootstrap
即可。
在package中引入相邻依赖
目前的项目结构如下:
monorepo/
packages/
module-a/
module-b/
如果想在module-b
中引入module-a
,执行如下命令即可
lerna add @monorepo/module-a --scope @monorepo/module-b
执行npm script
执行所有package中的scripts
命令
使用lerna的run
命令就可以在每个package中执行所包含的对应脚本,前提是需要先在package中写好公共的scripts
。
比如,若每个package均有test
script
"name": "@monorepo/module-a",
"scripts": {
"test": "jest"
}
则使用如下命令即可在每个package内执行测试:
lerna run test --stream
执行指定package中的scripts命令
需要使用--scope
过滤器来限定作用范围
比如,在project-alpha的package.json
中:
{
"name": "project-alpha",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"dev": "node index.js"
}
}
运行它的dev
命令需用下面的语句:
lerna exec --scope project-alpha -- yarn run dev
采用统一的规范配置
以husky和prettier为例
yarn add --dev husky prettier lint-staged -W
使用-W
选项会将依赖安装到workspace
的根目录下。
在根目录下正常设置相关配置文件
// .prettierrc
{
"singleQuote": true,
"jsxBracketSameLine": true,
"bracketSpacing": true,
"semi": true,
"arrowParens": "always",
"printWidth": 120
}
// package.json
{
...
"scripts": {
...
"precommit": "lint-staged",
...
},
"lint-staged": {
"packages/**/*.{js,jsx}": [
"prettier --write",
"git add"
]
},
...
"devDependencies": {
"husky": "^1.2.1",
"lerna": "^3.8.0",
"lint-staged": "^8.1.0",
"prettier": "^1.15.3"
}
}
测试
git commit -m "lint test"
这样,每次在根目录下执行git命令时会对里面的所有package进行lint
共用的devDependencies
多数package中共用的devDependencies
类型的库都可以提升到项目根目录中,这样做的好处有:
- 所有包使用相同版本的依赖,统一管理
- 可使用自动化工具让根目录下的依赖保持更新
- 减少依赖的安装时间,一次安装,多处使用
- 节省存储空间,安装在根目录的
node_module
下
提交与发布
与lerna
中版本控制及发布相关的概念与工具:
-
约定式提交
。一种源于AngularJS commit rules的提交规则。 -
用于从git元数据中生成
CHANGELOG.md
的工具,该工具仅当遵循Conventional Commits
规则时起作用。 -
lerna
中内置的一个工具,用于生成版本号、git标签、Conventional Changelog、发布的提交信息以及修改记录。可以在lerna.json
中将conventionalCommits
标记设为true
开启,该工具仅当遵循Conventional Commits
规则时起作用。 -
一个遵循
Conventional Commits
的commit信息格式化工具
具体的操作与demo可查看这篇文章
命令
command | value | options |
---|---|---|
lerna init |
创建一个新的lerna项目或将已存在项目改造为lerna项目 | --independent /-i |
lerna bootstrap |
当使用yarn 并开启了workspace 时等价于在根目录执行yarn install |
|
lerna import <pathToRepo> |
将本地路径<pathToRepo> 中的包导入到packages/<directory-name> ,并提交操作记录 |
|
lerna publish |
对更新后的包发布新版本;使用新版本号标记;升级所有npm和git中的库 | --npm-tag [tagname] , --canary/-c , --skip-git , --force-publish [packages] |
lerna change |
检查自上次发布以来改动的包 | |
lerna diff [package?] |
比较自上次发布以来的所有或指定的包 | |
lerna run [script] |
在每个包中执行一个npm script | |
lerna ls |
列出当前lerna项目中的public包 |
过滤器
用来过滤命令执行时的范围,详见@lerna/filter-options
filter | description |
---|---|
--scope <glob> |
仅包含glob所匹配到的package |
--ignore <glob> |
排除glob匹配到的package |
--no-private |
排除私有package,默认是包含的 |
注意,如果package想要使用npm script执行本地的可执行文件需要自己单独设置依赖。并且,在package的package.json
中,一般还需设置在runtime需要的依赖和一些公共的scripts
其他
- 跨项目本地开发
- TypeScript项目
参考
- 原文作者:yrq110
- 原文链接:http://yrq110.me/post/tool/study-on-lerna/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。