最近在看 vue-cli 的源码部分,注意到这一个仓库下维护了多个package,很好奇他是如何在一个repo中管理这些package的。
我们组现在也在使用组件库的方式维护项目间共用的业务代码。有两个组件库,存在依赖的关系,目前联调是通过
npm link
的方式,性能并不好,时常出现卡顿的问题。加上前一段时间组内分享vue3也提到了lerna,于是便决定仔细的调研一下这个工具,为接下里的组件库优化助力。
lerna 的文档还是很详细的,因为全是英文的,考虑到阅读问题,这里我先是自己跑了几个demo,然后做了中文翻译。后续我会出一篇专门的lerna实战篇
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
标准的 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
,除了可执行文件
lerna init
初始化 lerna 项目
-i, --independent 独立版本模式
[lerna create <name> [loc]](https://github.com/lerna/lern...
创建一个 packcage
--access
当使用scope package
时(@qinzhiwei/lerna),需要设置此选项 可选值: "public", "restricted"
--bin
创建可执行文件 --bin <executableName>
--description
描述 [字符串]
--dependencies
依赖,用逗号分隔 [数组]
--es-module
初始化一个转化的Es Module [布尔]
--homepage
源码地址 [字符串]
--keywords
关键字数 [数组]
--license
协议 字符串
--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
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
--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
$ 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"
lerna changed
列出自上次发布(打 tag)以来本地发生变化的 package
注意: lerna publish
和lerna version
的lerna.json
配置同样影响lerna changed
。 例如 command.publish.ignoreChanges
.
Command Options
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
lerna import <path-to-external-repository>
将现有的 package 导入到 lerna 项目中。可以保留之前的原始提交作者,日期和消息将保留。
注意:如果要在一个新的 lerna 中引入,必须至少有个 commit
Command Options
--flatten
处理合并冲突
--dest
指定引入包的目录
--preserve-commit
保持引入项目原有的提交者信息
lerna clean
lerna clean
移除所有 packages 下的 node_modules,并不会移除根目录下的
lerna diff
查看自上次发布(打 tag)以来某个 package 或者所有 package 的变化
$ lerna diff [package]
$ lerna diff
# diff a specific package
$ lerna diff package-name
Similar to lerna changed
. This command runs git diff
.
lerna exec
在每个 package 中执行任意命令,用波折号(--
)分割命令语句
$ lerna exec -- <command> [..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
Command Options
$ lerna exec --scope my-component -- ls -la
--concurrenty
使用给定的数量进行并发执行(除非指定了 --parallel
)。
输出是经过管道过滤,存在不确定性。
如果你希望命令一个接着一个执行,可以使用如下方式:
$ lerna exec --concurrency 1 -- ls -la
--stream
从子进程立即输出,前缀是包的名称。该方式允许交叉输出:
$ lerna exec --stream -- babel src -d lib
--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 <command>
默认情况下,如果一但出现命令报错就会退费进程。使用该命令会禁止此行为,跳过改报错行为,继续执行其他命令
--no-prefix
在输出中不显示 package 的名称
--profile
生成一个 json 文件,可以在 chrome 浏览器(devtools://devtools/bundled/devtools_app.html
)查看性能分析。通过配置--concurrenty
可以开启固定数量的子进程数量
$ lerna exec --profile -- <command>
注意: 仅在启用拓扑排序时分析。不能和 --parallel
and --no-sort
一同使用。
--profile-location <location>
设置分析文件存放位置
$ lerna exec --profile --profile-location=logs/profile/ -- <command>
lerna run
在每个 package 中运行 npm 脚本
$ lerna run <script> -- [..args] # runs npm run my-script in all packages that have it
$ lerna run test
$ lerna run build
# watch all packages and transpile on change, streaming prefixed output
$ lerna run --parallel watch
Command Options
--npm-client <client>
设置npm
客户端,默认是npm
$ lerna run build --npm-client=yarn
也可以在lerna.json
配置:
"command": {
"run": {
"npmClient": "yarn"
其余同lerna exec
lerna version
生成新的唯一版本号
bumm version:在使用类似 github 程序时,升级版本号到一个新的唯一值
lerna version 1.0.1 # 显示指定
lerna version patch # 语义关键字
lerna version # 从提示中选择
当执行时,该命令做了一下事情:
1.识别从上次打标记发布以来发生变更的 package 2.版本提示 3.修改 package 的元数据反映新的版本,在根目录和每个 package 中适当运行lifecycle scripts 4.在 git 上提交改变并对该次提交打标记(git commit
& git tag
) 5.提交到远程仓库(git push
)
Positionals
semver bump
lerna version [major | minor | patch | premajor | preminor | prepatch | prerelease]
# uses the next semantic version(s) value and this skips `Select a new version for...` prompt
When this positional parameter is passed, lerna version
will skip the version selection prompt and increment the version by that keyword.
You must still use the --yes
flag to avoid all prompts.
Prerelease
如果某些 package 是预发布版本(e.g. 2.0.0-beta.3
),当你运行lerna version
配合语义化版本时(major
, minor
, patch
),它将发布之前的预发布版本和自上次发布以来改变过的 packcage。
对于使用常规提交的项目,可以使用如下标记管理预发布版本:
--conventional-prerelease
: 发布当前变更为预发布版本(即便采用的是固定模式,也会单独升级该 package)
--conventional-graduate
: 升级预发布版本为稳定版(即便采用的是固定模式,也会单独升级该 package)
当一个 package 为预发版本时,不使用上述标记,使用lerna version --conventional-commits
,也会按照预发版本升级继续升级当前 package。
Command Options
--allow-branch
--amend
--changelog-preset
--conventional-commits
--conventional-graduate
--conventional-prerelease
--create-release
--exact
--force-publish
--git-remote
--ignore-changes
--ignore-scripts
--include-merged-tags
--message
--no-changelog
--no-commit-hooks
--no-git-tag-version
--no-granular-pathspec
--no-private
--no-push
--preid
--sign-git-commit
--sign-git-tag
--force-git-tag
--tag-version-prefix
--yes
--allow-branch <glob>
A whitelist of globs that match git branches where lerna version
is enabled.
It is easiest (and recommended) to configure in lerna.json
, but it is possible to pass as a CLI option as well.
设置可以调用lerna version
命令的分支白名单,也可以在lerna.json
中设置
"command": {
"version": {
"allowBranch": ["master", "beta/*", "feature/*"]
--amend
lerna version --amend
# commit message is retained, and `git push` is skipped.
默认情况下如果暂存区有未提交的内容,lerna version
会失败,需要提前保存本地内容。使用该标记可以较少 commit 的次数,将当前变更内容随着本次版本变化一次 commit。并且不会git push
--changelog-preset
lerna version --conventional-commits --changelog-preset angular-bitbucket
默认情况下,changelog 预设设置为angular
。在某些情况下,您可能需要使用另一个预置或自定义。
--conventional-commits
lerna version --conventional-commits
当使用这个标志运行时,lerna 版本将使用传统的提交规范/Conventional Commits Specification来确定版本并生成CHANGELOG.md。
传入 --no-changelog
将阻止生成或者更新CHANGELOG.md
.
--conventional-graduate
lerna version --conventional-commits --conventional-graduate=package-2,package-4
# force all prerelease packages to be graduated
lerna version --conventional-commits --conventional-graduate
但使用该标记时,lerna vesion
将升级指定的 package(用逗号分隔)或者使用*
指定全部 package。和--force-publish
很像,无论当前的 HEAD 是否发布,该命令都会起作用,任何没有预发布的 package 将会被忽略。如果未指定的包(如果指定了包)或未预先发布的包发生了更改,那么这些包将按照它们通常使用的--conventional-commits
进行版本控制。
"升级"一个包意味着将一个预发布的包升级为发布版本,例如package-1@1.0.0-alpha.0 => package-1@1.0.0
。
注意: 当指定包时,指定包的依赖项将被释放,但不会被“升级”。必须和--conventional-commits
一起使用
--conventional-prerelease
lerna version --conventional-commits --conventional-prerelease=package-2,package-4
# force all changed packages to be prereleased
lerna version --conventional-commits --conventional-prerelease
当使用该标记时,lerna version
将会以预发布的版本发布指定的 package(用逗号分隔)或者使用*
指定全部 package。
--create-release <type>
lerna version --conventional-commits --create-release github
lerna version --conventional-commits --create-release gitlab
当使用此标志时,lerna version
会基于改变的 package 创建一个官方正式的 GitHub 或 GitLab 版本记录。需要传递--conventional-commits
去创建 changlog。
GithuB 认证,以下环境变量需要被定义。
GH_TOKEN
(required) - Your GitHub authentication token (under Settings > Developer settings > Personal access tokens).
GHE_API_URL
- When using GitHub Enterprise, an absolute URL to the API.
GHE_VERSION
- When using GitHub Enterprise, the currently installed GHE version. Supports the following versions.
GitLab 认证,以下环境变量需要被定义。
GL_TOKEN
(required) - Your GitLab authentication token (under User Settings > Access Tokens).
GL_API_URL
- An absolute URL to the API, including the version. (Default: https://gitlab.com/api/v4)
注意: 不允许和--no-changelog
一起使用
这个选项也可以在lerna.json
中配置:
"changelogPreset": "angular"
If the preset exports a builder function (e.g. conventional-changelog-conventionalcommits
), you can specify the preset configuration too:
"changelogPreset": {
"name": "conventionalcommits",
"issueUrlFormat": "{{host}}/{{owner}}/{{repository}}/issues/{{id}}"
--exact
lerna version --exact
--force-publish
lerna version --force-publish=package-2,package-4
# force all packages to be versioned
lerna version --force-publish
强制更新版本
这个操作将跳过lerna changed
检查,即便 package 没有做任何变更也会更新版本
--git-remote <name>
lerna version --git-remote upstream
将本地commit
push 到指定的远程残酷,默认是origin
--ignore-changes
变更检测时忽略的文件
lerna version --ignore-changes '**/*.md' '**/__tests__/**'
建议在根目录lerna.json
中配置:
"ignoreChanges": ["**/__fixtures__/**", "**/__tests__/**", "**/*.md"]
--no-ignore-changes
禁止任何现有的忽略配置:
--ignore-scripts
--include-merged-tags
lerna version --include-merged-tags
--message <msg>
-m
别名,等价于git commit -m
lerna version -m "chore(release): publish %s"
# commit message = "chore(release): publish v1.0.0"
lerna version -m "chore(release): publish %v"
# commit message = "chore(release): publish 1.0.0"
# When versioning packages independently, no placeholders are replaced
lerna version -m "chore(release): publish"
# commit message = "chore(release): publish
# - package-1@3.0.1
# - package-2@1.5.4"
也可以在lerna.json
配置:
"command": {
"version": {
"message": "chore(release): publish %s"
--no-changelog
lerna version --conventional-commits --no-changelog
不生成CHANGELOG.md
。
注意:不可以和--create-release
一起使用
--no-commit-hooks
默认情况下,lerna version
会运行git commit hooks
。使用该标记,阻止git commit hooks
运行。
--no-git-tag-version
默认情况下,lerna version
会提交变更到package.json
文件,并打标签。使用该标记会阻止该默认行为。
--no-granular-pathspec
默认情况下,在创建版本的过程中,会执行git add -- packages/*/package.json
操作。
也可以更改默认行为,提交除了package.json
以外的信息,前提是必须做好敏感数据的保护。
// leran.json
"version": "independent",
"granularPathspec": false
--no-private
排除private:true
的 package
--no-push
By default, lerna version
will push the committed and tagged changes to the configured git remote.
Pass --no-push
to disable this behavior.
--preid
lerna version prerelease
# uses the next semantic prerelease version, e.g.
# 1.0.0 => 1.0.1-alpha.0
lerna version prepatch --preid next
# uses the next semantic prerelease version with a specific prerelease identifier, e.g.
# 1.0.0 => 1.0.1-next.0
版本语义化
--sign-git-commit
npm version
option
--sign-git-tag
npm version
option
--force-git-tag
取代已存在的tag
--tag-version-prefix
自定义版本前缀。默认为v
# locally
lerna version --tag-version-prefix=''
# on ci
lerna publish from-git --tag-version-prefix=''
--yes
lerna version --yes
# skips `Are you sure you want to publish these packages?`
跳过所有提示
生成更新日志CHANGELOG.md