lerna
最近在看[vue-cli](https://github.com/vuejs/vue-cli的源码部分,注意到这一个仓库下维护了多个package,很好奇他是如何在一个repo中管理这些package的。
我们组现在也在使用组件库的方式维护项目间共用的业务代码。有两个组件库,存在依赖的关系,目前联调是通过npm link
的方式,性能并不好,时常出现卡顿的问题。加上前一段时间组内分享vue3也提到了lerna,于是便决定仔细的调研一下这个工具,为接下里的组件库优化助力。
lerna的文档还是很详细的,因为全是英文的,考虑到阅读问题,这里我先是自己跑了几个demo,然后做了中文翻译。后续我会出一篇专门的lerna实战篇
[demo](https://github.com/qinzhiwei1993/lerna-repo-test
lerna 是干什么的?
Lerna 是一个工具,它优化了使用 git 和 npm 管理多包存储库的工作流。
背景
1.将一个大的 package 分割成一些小的 packcage 便于分享,调试
2.在多个 git 仓库中更改容易变得混乱且难以跟踪
3.在多个 git 仓库中维护测试繁琐
两种工作模式
Fixed/Locked mode (default)
vue,babel 都是用这种,在 publish 的时候,所有的包版本都会更新,并且包的版本都是一致的,版本号维护在 lerna.jon 的 version 中
Independent mode
lerna init --independent
独立模式,每个 package 都可以有自己的版本号。版本号维护在各自 package.json 的 version 中。每次发布前都会提示已经更改的包,以及建议的版本号或者自定义版本号。这种方式相对第一种来说,更灵活
初始化项目
npm install -g lerna // 这里是全局安装,也可以安装为项目开发依赖,使用全局方便后期使用命令行
mkdir lerna-repo
cd lerna-repo
lerna init // 初始化一个lerna项目结构,如果希望各个包使用单独版本号可以加 -i | --independent
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-znl8Z0YA-1599646450309)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]
标准的 lerna 目录结构
-
每个单独的包下都有一个 package.json 文件
-
如果包名是带 scope 的,例如@test/lerna,package.json 中,必须配置"publishConfig": {“access”: “public”}
my-lerna-repo/
package.json
lerna.json
LICENSE
packages/
package-1/
package.json
package-2/
package.json
启用 yarn Workspaces (强烈建议)
Workspaces can only be enabled in private projects.
默认是 npm, 每个子 package 下都有自己的 node_modules,通过这样设置后,会把所有的依赖提升到顶层的 node_modules 中,并且在 node_modules 中链接本地的 package,便于调试
注意:必须是 private 项目才可以开启 workspaces
// package.json
"private": true,
"workspaces": [
"packages/*"
],
// lerna.json
"useWorkspaces": true,
"npmClient": "yarn",
hoist: 提取公共的依赖到根目录的node_moduels
,可以自定义指定。其余依赖安装的package/node_modeles
中,可执行文件必须安装在package/node_modeles
。
workspaces: 所有依赖全部在跟目录的node_moduels
,除了可执行文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wk3liN9K-1599646450317)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]
常用命令
lerna init
初始化 lerna 项目
- -i, --independent 独立版本模式
lerna create [loc]
创建一个 packcage
-
--access
当使用scope package
时(@qinzhiwei/lerna),需要设置此选项 [可选值: “public”, “restricted”][默认值: public] -
--bin
创建可执行文件--bin <executableName>
-
--description
描述 [字符串] -
--dependencies
依赖,用逗号分隔 [数组] -
--es-module
初始化一个转化的Es Module [布尔] -
--homepage
源码地址 [字符串] -
--keywords
关键字数 [数组] -
--license
协议 [字符串][默认值: isc] -
--private
是否私有仓库 [布尔] -
--registry
源 [字符串] -
--tag
发布的标签 [字符串] -
-y, --yes
跳过所有的提示,使用默认配置 [布尔]
lerna add
为匹配的 package 添加本地或者远程依赖,一次只能添加一个依赖
$ lerna add <package>[@version] [--dev] [--exact] [--peer]
运行该命令时做的事情:
-
为匹配到的 package 添加依赖
-
更改每个 package 下的 package.json 中的依赖项属性
Command Options
以下几个选项的含义和npm install
时一致
-
--dev
-
--exact
-
--peer
同级依赖,使用该package需要在项目中同时安装的依赖 -
--registry <url>
-
--no-bootstrap
跳过lerna bootstrap
,只在更改对应的 package 的 package.json 中的属性
Examples
# Adds the module-1 package to the packages in the 'prefix-' prefixed folders
lerna add module-1 packages/prefix-*
# Install module-1 to module-2
lerna add module-1 --scope=module-2
# Install module-1 to module-2 in devDependencies
lerna add module-1 --scope=module-2 --dev
# Install module-1 to module-2 in peerDependencies
lerna add module-1 --scope=module-2 --peer
# Install module-1 in all modules except module-1
lerna add module-1
# Install babel-core in all modules
lerna add babel-core
lerna bootstrap
将本地 package 链接在一起并安装依赖
执行该命令式做了一下四件事:
1.为每个 package 安装依赖
2.链接相互依赖的库到具体的目录,例如:如果 lerna1 依赖 lerna2,且版本刚好为本地版本,那么会在 node_modules 中链接本地项目,如果版本不满足,需按正常依赖安装
3.在 bootstraped packages 中 执行
npm run prepublish
4.在 bootstraped packages 中 执行
npm run prepare
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENY4EVrV-1599646450349)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DVSIBjOD-1599646450351)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]
Command Options
-
--hoist
匹配 [glob] 依赖 提升到根目录 [默认值: ‘**’], 包含可执行二进制文件的依赖项还是必须安装在当前 package 的 node_modules 下,以确保 npm 脚本的运行 -
--nohoist
和上面刚好相反 [字符串] -
--ignore-prepublish
在 bootstraped packages 中不再运行 prepublish 生命周期中的脚本 [布尔] -
--ignore-scripts
在 bootstraped packages 中不再运行任何生命周期中的脚本 [布尔] -
--npm-client
使用的 npm 客户端(npm, yarn, pnpm, …) [字符串] -
--registry
源 [字符串] -
--strict
在 bootstrap 的过程中不允许发出警告,避免花销更长的时间或者导致其他问题 [布尔] -
--use-workspaces
启用 yarn 的 workspaces 模式 [布尔] -
--force-local
无论版本范围是否匹配,强制本地同级链接 [布尔] -
--contents
子目录用作任何链接的源。必须适用于所有包 [字符串][默认值: .]
lerna link
将本地相互依赖的 package 相互连接。例如 lerna1 依赖 lerna2,且版本号刚好为本地的 lerna2,那么会在 lerna1 下 node_modules 中建立软连指向 lerna2
Command Options
- –force-local 无论本地 package 是否满足版本需求,都链接本地的
// 指定软链到package的特定目录
"publishConfig": {
"directory": "dist" // bootstrap的时候软链package下的dist目录 package-1/dist => node_modules/package-1
}
lerna list
list 子命令
-
lerna ls
: 等同于lerna list
本身,输出项目下所有的 package -
lerna ll
: 输出项目下所有 package 名称、当前版本、所在位置 -
lerna la
: 输出项目下所有 package 名称、当前版本、所在位置,包括 private package
Command Options
--json
以 json 形式展示
$ lerna ls --json
[
{
"name": "package-1",
"version": "1.0.0",
"private": false,
"location": "/path/to/packages/pkg-1"
},
{
"name": "package-2",
"version": "1.0.0",
"private": false,
"location": "/path/to/packages/pkg-2"
}
]
--ndjson
$ lerna ls --ndjson
{"name":"package-1","version":"1.0.0","private":false,"location":"/path/to/packages/pkg-1"}
{"name":"package-2","version":"1.0.0","private":false,"location":"/path/to/packages/pkg-2"}
--all
Alias: -a
显示默认隐藏的 private package
$ lerna ls --all
package-1
package-2
package-3 (private)
--long
Alias: -l
显示包的版本、位置、名称
$ lerna ls --long
package-1 v1.0.1 packages/pkg-1
package-2 v1.0.2 packages/pkg-2
$ lerna ls -la
package-1 v1.0.1 packages/pkg-1
package-2 v1.0.2 packages/pkg-2
package-3 v1.0.3 packages/pkg-3 (private)
--parseable
Alias: -p
显示包的绝对路径
In --long
output, each line is a :
-separated list: <fullpath>:<name>:<version>[:flags..]
$ lerna ls --parseable
/path/to/packages/pkg-1
/path/to/packages/pkg-2
$ lerna ls -pl
/path/to/packages/pkg-1:package-1:1.0.1
/path/to/packages/pkg-2:package-2:1.0.2
$ lerna ls -pla
/path/to/packages/pkg-1:package-1:1.0.1
/path/to/packages/pkg-2:package-2:1.0.2
/path/to/packages/pkg-3:package-3:1.0.3:PRIVATE
--toposort
按照拓扑顺序(dependencies before dependents)对包进行排序,而不是按目录对包进行词法排序。
$ json dependencies <packages/pkg-1/package.json
{
"pkg-2": "file:../pkg-2"
}
$ lerna ls --toposort
package-2
package-1
--graph
将依赖关系图显示为 JSON 格式的邻接表 adjacency list.
$ lerna ls --graph
{
"pkg-1": [
"pkg-2"
],
"pkg-2": []
}
$ lerna ls --graph --all
{
"pkg-1": [
"pkg-2"
],
"pkg-2": [
"pkg-3"
],
"pkg-3": [
"pkg-2"
]
}