用于管理包含多个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项目
yarn global add lerna mkdir monorepo && cd monorepo lerna init
-
创建package
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
中版本控制及发布相关的概念与工具:
具体的操作与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