Lerna 入门级实践

758 阅读8分钟

本文主要总结了本人在使用 lerna 进行多包管理中常用的一些命令详解,帮助刚刚接触 lerna 的朋友快速上手搭建可用的 lerna 仓库工作流。Enjoy it :)

About lerna

Lerna 是一个可以用来管理多个 JavaScript 项目 package 的工具。当项目中遇到将一个大项目仓库拆分成 monorepos,将这些 package 分散存放在独立的项目仓库之中,但是这会使得代码变更变得难以追踪,同时,由于多包之间存在复杂的依赖关系,这种也会使得包与包之间依赖管理变得混乱。而 Lerna 提供了一种新的思路,Lerna 是针对使用 git 和 npm 管理多包代码仓库的流程进行优化的工具 (Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.)

Init lerna

  1. 创建一个 git 仓库,并 clone 到本地,进入该 git 仓库目录

  2. 可以全局安装或者通过 npm 依赖安装 lerna,并执行 lerna init 将仓库转为 lerna 仓库

    • attention: 如果执行 init 时加上 --independent 参数,lerna 将会以独立版本控制的模式进行管理,即每个包将会是独立的版本号。
  3. 执行之后,该仓库目录变成了如下的结构

    lerna-repo
        // 根目录的 package.json
        package.json
    
        // lerna 配置
        lerna.json
    
        // npm包具体放置目录
        packages/
            package-1/
                package.json 
            package-2/
                package.json
    

本人的 Workflow

我们项目会分为好几个版本进行部署测试,如 dev、test、pro。所有人开发的特性分支都需要合入到 dev 中,dev 验收通过之后,再合入至 test。 在 test 测试或者修复测试之后,再合入到 pro,这些主干分支都是单向流动 dev-> test-> pro-> dev 。只有在 dev、test 环境都验收之后,才会让 pro 的代码正式上岗,正式发布。

Lerna 日常使用命令

完成初始化 lerna 仓库之后,接下来就要开始进行 lerna 的管理工作。这里仅总结了日常部分 lerna 使用过程中常用的指令以及常用的指令参数。具体可以参阅 Lerna commands-github,这个目录下会详细介绍每个命令执行的内容,以及相关参数的使用。

lerna bootstrap

keyword: install & symlink

这个命令主要是用来执行 packages/ 目录下每个包的 npm install 命令,并将这些包直接的相互依赖关系进行链接。当执行这个命令时,这个命令会执行:

  1. npm install 安装所有包的外部依赖;
  2. 将目录下有依赖关系的包通过软链的方式连接在一块,这一步类似于 npm link 的作用;
  3. 执行所有“被准备过”的 package 包的 npm run prepublish,以运行 prepublish 生命周期下的脚本
  4. 执行所有“被准备过”的 package 包的 npm run prepare,以运行 prepare 生命周期的脚本
  • --hoist

    当 packages/ 多个包有相同依赖时,可以通过加入参数 --hoist 将公共依赖的部分 (Mostly-common dependencies) 提至项目顶层的 node_modules,以减少空间冗余。但是这种提升公共依赖的方法,可能会引入一些副作用。

    1. Module resolution:当遇到没有遵循 查找 NPM 依赖关系 的项目或者第三方库时,可能会在使用过程中报错,提示找不到该模块。
    2. Forgetting dependencies: 如果开发人员出现疏忽,未在该 A 包中的声明该依赖,而在其他包中声明,由于第三方依赖被提升到项目 node_modules ,导致在本地环境中可用。而当 A 包单独发布时,由于未找到该依赖关系,导致实际使用过程中报错。

lerna version

keyword: update version from last release

这个命令主要会检查自上一次发布以来,仓库内发生了哪一些更新,并更新版本号。当执行这个命令时,主要会执行,详细的执行内容可以参阅 version life script

  1. 检查自上一个版本依赖的更新内容
  2. 根据配置,更新版本号
  3. 执行声明周期脚本
  4. git commit 变更,以及对当前版本打上一个 tag
  5. git push 推送到远端仓库

通用配置

  • --allow-branch <branch-name>

    这个命令相当于是一个可执行 lerna version 的分支白名单,如果未在这个白名单内,则无法执行该命令。lerna 会检查白名单分支,如果当前本地仓库分支未在该白名单列表内,则会被打断执行。这个配置通常在主干模型中十分有用,当限制只能由某些分支发布版本,避免任意分支都可以发布版本带来的混乱。<branch-name> 支持 glob 匹配。

    这个命令通常可以在 lerna.json 中配置:

    {
        command: {
            version: {
                "allowBranch": ["master", "bugfix/*"]
            }
        }
    }
    

    需要注意的是,执行这个命令时,例如:lerna version --allow-branch dev,lerna.json 的配置将会被覆盖,所以这个命令需要谨慎执行。

  • --conventional-commits

    在执行命令时,加上这个 flag,会让 lerna version 遵循 Conventional Commits 规定,来生成对应的版本号以及 CHANGELOG.md 文件。

    注意:如果同时使用了 --no--changelog 则会阻止该指令生成或者更新已有的 CHANGELOG.md 文件。

  • --ignore-changes <glob>

    在加入这个 flag 之后,lerna 在检查更新时,会默认忽视这些文件的更新。这个 flag 支持 glob 的匹配,在 lerna.json 中配置,则会默认忽视所有 _test_ 目录下的文件,详细的 glob 匹配可以自行查询相关写法。

    {
        "ignoreChanges": ["**/_test_/**"]
    }
    
  • --message <msg>

    这个 flag 实际上是 git commit -m 一样的作用, 用来填写 commit message 的内容。lerna 提供了 %s%v 两个占位符,使用 %s 则为带 'v' 的版本号,如 v0.0.1; 如果使用 %v 则不带 'v', e.g 0.0.1

  • --yes

    默认在交互式输入时,输入 yes,以跳过交互式输入的内容,在 CI 流水线中很有用。

  • --no-commit-hooks

    这个指令可以在配置了例如 husky 之类的 git commit 钩子的仓库中使用,用来禁止 lerna version 在执行 git commit 的过程中触发相关的钩子,例如 commitmsg-lint 之类的钩子。

预发布

  • --preid <tag>

    这个 flag 实际上可以规定 pre-release 版本的前缀,如 lerna version --preid alpha,则默认预发布版本为 alpha-0 开始,如 0.0.1 -> 0.1.0-alpha.0。通常在使用过程中,可以和 lerna publish --dist-tag <tag> 搭配使用,这个参数和 lerna publish 中一致。

  • --conventional-prerelease

    将当前版本是作为一个 prerelease 的版本,而非直接升级大的稳定版本。在多分支开发中比较常用,例如有一个 dev、test、最终到 pro 正式发布,前置的测试流程可以使用这个 flag 来标记,这是一个预发布版本。也可以在后面带上参数,如 --conventional-prerelease=package1,package2 ,指定 package1,package2 为预发布版本。

    当搭配了 --conventional-commits,则版本号的升级规则会遵循语义化版本号 Conventional Commits 规定。

正式发布

  • --conventional-graduate

    如果曾经使用过 pre-release 则需要在正式发布的时候,加上这个 flag,让后续的 lerna publish 运作正确。

    注意:如果使用过 pre-release, 而在正式版本发布前的 lerna version 执行时,未加上 --conventional-graduate。 则 lerna 默认会在之前的版本后面 +1, 即如果是 0.0.1-alpha.0 就会变成 0.0.1-alpha.1

lerna publish

keyword: publish in current repo

  • --from-package or --from-git

    • 当没有加任何 flag 时,lerna publish 默认会发布自上次更新以来有更新的包;
    • 加上 --from-package,则会发布在 registry 没有的更新作为新的版本,当出现发布失败的情况,未能将包发布到 registry 时,这个指令将会非常有用;
    • 加上 --from-git, 则会发布当前 tagged 的 commit 作为新的版本。

    所有版本发布过程中,仍会正常执行 npm 的 lifecyle script,除非在 lerna 中被 ignore-scripts 手动禁止执行。

  • --cananry

    加上这个标记之后, lerna 会以更加“精细”的版本命名来发布包,默认会在元后缀之后,附加当前的 git sha。例如 1.0.0 变为 1.1.0-alpha.0 + 77d227e3

  • --preid

    lerna version 的同名参数不同,这个 flag 只在 --cannry 计算中生效,作为创建一个新的元后缀。但是,建议在实际过程中,如果均使用了 preid,则两个 preid 应保持一致。

  • --dist-tag <tag>

    使用此 flag 运行时, lerna publish 会使用给定的标记(默认为 lastest) 进行发布,这个选项在有多环境测试时,非常有用。可以避免用户升级到了 pre-release 的版本。如果用户是直接 npm install pkg1 则会下载到最新的稳定版本。如果用户需要特定的 feature 版本,或者是内部测试时,可以指定某个 tag,通过 npm install pkg1@<tag-name> 进行下载。

    🌰 在本人工作流的例子中,我们指定 dev 的 dist tag 为 alpha,则测试朋友可以通过下载 npm install pkg1@alpha 来下载最新的 dev 环境版本。

  • --yes

    默认在交互式输入时,输入 yes,以跳过交互式输入的内容,在 CI 流水线中很有用。