相关文章推荐
淡定的米饭  ·  GLES2.0。通过glEGLImageTa ...·  1 年前    · 
酷酷的牙膏  ·  Oracle&C# 导出多Sheet ...·  1 年前    · 
彷徨的绿茶  ·  mediawiki ...·  1 年前    · 
发呆的黑框眼镜  ·  python - What ...·  1 年前    · 
幸福的骆驼  ·  Python中Nan||None||NaT| ...·  1 年前    · 

最近在看 vue-cli 的源码部分,注意到这一个仓库下维护了多个package,很好奇他是如何在一个repo中管理这些package的。

我们组现在也在使用组件库的方式维护项目间共用的业务代码。有两个组件库,存在依赖的关系,目前联调是通过 npm link 的方式,性能并不好,时常出现卡顿的问题。加上前一段时间组内分享vue3也提到了lerna,于是便决定仔细的调研一下这个工具,为接下里的组件库优化助力。

lerna 的文档还是很详细的,因为全是英文的,考虑到阅读问题,这里我先是自己跑了几个demo,然后做了中文翻译。后续我会出一篇专门的lerna实战篇

demo 原文

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

标准的 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

    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: <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 publishlerna versionlerna.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配合语义化版本时(majorminorpatch),它将发布之前的预发布版本和自上次发布以来改变过的 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
    

    将本地commitpush 到指定的远程残酷,默认是origin

    --ignore-changes

    变更检测时忽略的文件

    lerna version --ignore-changes '**/*.md' '**/__tests__/**'
    

    建议在根目录lerna.json中配置:

    "ignoreChanges": ["**/__fixtures__/**", "**/__tests__/**", "**/*.md"]

    --no-ignore-changes 禁止任何现有的忽略配置:

    --ignore-scripts

    禁止lifecycle 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

    如果你在使用多包存储一段时间后,开始使用--conventional-commits标签,你也可以使用conventional-changelog-cli 和 lerna exec为之前的版本创建 changelog:

    # Lerna does not actually use conventional-changelog-cli, so you need to install it temporarily
    npm i -D conventional-changelog-cli
    # Documentation: `npx conventional-changelog --help`
    # fixed versioning (default)
    # run in root, then leaves
    npx conventional-changelog --preset angular --release-count 0 --outfile ./CHANGELOG.md --verbose
    npx lerna exec --concurrency 1 --stream -- 'conventional-changelog --preset angular --release-count 0 --commit-path $PWD --pkg $PWD/package.json --outfile $PWD/CHANGELOG.md --verbose'
    # independent versioning
    # (no root changelog)
    npx lerna