最近在看[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 是一个工具,它优化了使用 git 和 npm 管理多包存储库的工作流。
1.将一个大的 package 分割成一些小的 packcage 便于分享,调试
2.在多个 git 仓库中更改容易变得混乱且难以跟踪
3.在多个 git 仓库中维护测试繁琐
vue,babel 都是用这种,在 publish 的时候,所有的包版本都会更新,并且包的版本都是一致的,版本号维护在 lerna.jon 的 version 中
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])]
每个单独的包下都有一个 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
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 项目
创建一个 packcage
--access
当使用scope package
时(@qinzhiwei/lerna),需要设置此选项 [可选值: “public”, “restricted”][默认值: public]
--bin
创建可执行文件 --bin
--description
描述 [字符串]
--dependencies
依赖,用逗号分隔 [数组]
--es-module
初始化一个转化的Es Module [布尔]
--homepage
源码地址 [字符串]
--keywords
关键字数 [数组]
--license
协议 [字符串][默认值: isc]
--private
是否私有仓库 [布尔]
--registry
源 [字符串]
--tag
发布的标签 [字符串]
-y, --yes
跳过所有的提示,使用默认配置 [布尔]
为匹配的 package 添加本地或者远程依赖,一次只能添加一个依赖
$ lerna add [@version] [--dev] [--exact] [--peer]
运行该命令时做的事情:
为匹配到的 package 添加依赖
更改每个 package 下的 package.json 中的依赖项属性
以下几个选项的含义和npm install
时一致
--dev
--exact
--peer
同级依赖,使用该package需要在项目中同时安装的依赖
--registry
--no-bootstrap
跳过 lerna bootstrap
,只在更改对应的 package 的 package.json 中的属性
所有的过滤选项都支持
# 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
将本地 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])]
--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
子目录用作任何链接的源。必须适用于所有包 [字符串][默认值: .]
将本地相互依赖的 package 相互连接。例如 lerna1 依赖 lerna2,且版本号刚好为本地的 lerna2,那么会在 lerna1 下 node_modules 中建立软连指向 lerna2
// 指定软链到package的特定目录
"publishConfig": {
"directory": "dist" // bootstrap的时候软链package下的dist目录 package-1/dist => node_modules/package-1
}
lerna ls
: 等同于 lerna list
本身,输出项目下所有的 package
lerna ll
: 输出项目下所有 package 名称、当前版本、所在位置
lerna la
: 输出项目下所有 package 名称、当前版本、所在位置,包括 private package
--json
--ndjson
-a
, --all
-l
, --long
-p
, --parseable
--toposort
--graph
所有的过滤选项都支持
--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
以newline-delimited JSON展示信息
$ 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:
$ 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
--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"
]
}
列出自上次发布(打 tag)以来本地发生变化的 package
注意: lerna publish
和lerna version
的lerna.json
配置同样影响lerna changed
。 例如 command.publish.ignoreChanges
.
lerna changed
支持 lerna ls
的所有标记:
--json
--ndjson
-a
, --all
-l
, --long
-p
, --parseable
--toposort
--graph
lerna 不支持过滤选项, 因为lerna version
or lerna publish
不支持过滤选项.
lerna changed
支持 lerna version
(the others are irrelevant)的过滤选项:
--conventional-graduate
.
--force-publish
.
--ignore-changes
.
--include-merged-tags
.
lerna import
将现有的 package 导入到 lerna 项目中。可以保留之前的原始提交作者,日期和消息将保留。
注意:如果要在一个新的 lerna 中引入,必须至少有个 commit
--flatten
处理合并冲突
--dest
指定引入包的目录
--preserve-commit
保持引入项目原有的提交者信息
lerna clean
移除所有 packages 下的 node_modules,并不会移除根目录下的
所有的过滤选项都支持
查看自上次发布(打 tag)以来某个 package 或者所有 package 的变化
$ lerna diff [package]
$ lerna diff
# diff a specific package
$ lerna diff package-name
Similar to
lerna changed
. This command runsgit diff
.
在每个 package 中执行任意命令,用波折号(--
)分割命令语句
$ lerna exec -- [..args] # runs the command in all packages
$ lerna exec -- rm -rf ./node_modules
$ lerna exec -- protractor conf.js
可以通过LERNA_PACKAGE_NAME
变量获取当前 package 名称:
$ lerna exec -- npm view $LERNA_PACKAGE_NAME
也可以通过LERNA_ROOT_PATH
获取根目录绝对路径:
$ lerna exec -- node $LERNA_ROOT_PATH/scripts/some-script.js
所有的过滤选项都支持
$ lerna exec --scope my-component -- ls -la
使用给定的数量进行并发执行(除非指定了
--parallel
)。
输出是经过管道过滤,存在不确定性。
如果你希望命令一个接着一个执行,可以使用如下方式:
$ lerna exec --concurrency 1 -- ls -la
--stream
从子进程立即输出,前缀是包的名称。该方式允许交叉输出:
$ lerna exec --stream -- babel src -d lib
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jh92cyxU-1599646450354)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]
--parallel
和--stream
很像。但是完全忽略了并发性和排序,立即在所有匹配的包中运行给定的命令或脚本。适合长时间运行的进程。例如处于监听状态的babel src -d lib -w
$ lerna exec --parallel -- babel src -d lib -w
注意: 建议使用命令式控制包的范围。
因为过多的进程可能会损害
shell
的稳定。例如最大文件描述符限制
--no-bail
# Run a command, ignoring non-zero (error) exit codes
$ lerna exec --no-bail
默认情况下,如果一但出现命令报错就会退费进程。使用该命令会禁止此行为,跳过改报错行为,继续执行其他命令
--no-prefix
在输出中不显示 package 的名称
--profile
生成一个 json 文件,可以在 chrome 浏览器(devtools://devtools/bundled/devtools_app.html
)查看性能分析。通过配置--concurrenty
可以开启固定数量的子进程数量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-230mu6uR-1599646450356)(https://github.com/qinzhiwei1993/lerna-repo-test/raw/master/images/[email protected])]
$ lerna exec --profile --
注意: 仅在启用拓扑排序时分析。不能和
--parallel
and--no-sort
一同使用。
--profile-location
设置分析文件存放位置
$ lerna exec --profile --profile-location=logs/profile/ --
在每个 package 中运行 npm 脚本
$ lerna run