使用 Lerna 管理模块

原文

www.lishuaishuai.com/engineering…html

前言

对于维护过多个package的同窗来讲,都会遇到一个选择:这些package是放在一个仓库里维护仍是放在多个仓库里单独维护,数量较少的时候,多个仓库维护不会有太大问题,可是当package数量逐渐增多时,一些问题逐渐暴露出来:前端

  1. package之间相互依赖,开发人员须要在本地手动执行npm link,维护版本号的更替;
  2. issue难以统一追踪,管理,由于其分散在独立的repo里;
  3. 每个package都包含独立的node_modules,并且大部分都包含babel,webpack等开发时依赖,安装耗时冗余而且占用过多空间。

Monorepo vs Multirepo

Monorepo 的全称是 monolithic repository,即单体式仓库,与之对应的是 Multirepo (multiple repository),这里的“单”和“多”是指每一个仓库中所管理的模块数量。node

Multirepo 是比较传统的作法,即每个模块都单独用一个仓库来进行管理,典型案例有 webpack,优缺点总结以下:webpack

优势:git

  • 各模块管理自由度较高,可自行选择构建工具,依赖管理,单元测试等配套设施
  • 各模块仓库体积通常不会太大

缺点:github

  • issue 管理混乱,在实际使用中会发现 core repo 中常常会出现对一些针对 module 提出的问题,须要作 issue 迁移或关联
  • changlog 没法关联,没法很好的自动关联各个 module 与 core repo 之间的变更联系
  • 版本更新繁琐,若是 core repo 的版本发生了变化,须要对全部的 module 进行依赖 core repo 的更新
  • 测试复杂,对多个相关联 module 测试繁琐

Monorep 是把全部相关的 module 都放在一个仓库里进行管理,每一个 module 独立发布,典型案例有 babel,优缺点总结以下:web

优势:npm

  • 管理简便,issue 和 PR 都放在一个仓库中进行维护
  • changelog 维护简便,全部changelog 都基于同一份 commit 列表
  • 版本更新简便,core repo 以及各模块版本发生变动后能够很简便的同步更新其他全部对其有依赖的 module

缺点:json

  • 仓库体积增加迅速,随着 module 的增多,仓库的体积会变得十分庞大
  • 自由度较低,高度的统一致使各个模块的自由度较低,且对统一的配套工具(构建,测试)等要求较高,要能适配各个 module 的要求

Lerna 是什么

A tool for managing JavaScript projects with multiple packages.bootstrap

Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.

Lerna 是一个管理多个 npm 模块的工具,是 Babel 本身用来维护本身的 Monorepo 并开源出的一个项目。优化维护多包的工做流,解决多个包互相依赖,且发布须要手动维护多个包的问题。Lerna 如今已经被不少著名的项目组织使用,如:Babel, React, Vue, Angular, Ember, Meteor, Jest 。

一个基本的 Lerna 仓库结构以下:

my-lerna-repo/
    ┣━ packages/
    ┃     ┣━ package-1/
    ┃     ┃      ┣━ ...
    ┃     ┃      ┗━ package.json
    ┃     ┗━ package-2/
    ┃            ┣━ ...
    ┃            ┗━ package.json
    ┣━ ...
    ┣━ lerna.json
    ┗━ package.json
复制代码

使用

初始化

全局安装 lerna,再执行相关命令

$ npm i -g lerna
$ mkdir lerna-repo && cd $_
$ lerna init
复制代码

lerna init 命令会建立一个用来配置的 lerna.json,文件以及用于存放全部 modulepackages 文件夹,以下:

lerna-repo/
    ┣━ packages/
    ┣━ lerna.json
    ┗━ package.json
复制代码

Lerna 提供两种不一样的方式来管理你的项目:FixedIndependent,默认采用 Fixed 模式,若是你想采用 Independent 模式,只需在执行 init 命令的时候加上 --independent-i 参数便可。

Fixed/Locked 模式(默认) 固定模式下 Lerna 项目在单一版本线上运行。版本号保存在项目根目录下 lerna.json 文件中的 version 下。当你运行 lerna publish 时,若是一个模块自上次发布版本之后有更新,则它将更新到你将要发布的新版本。这意味着你在须要发布新版本时只需发布一个统一的版本便可。

Independent 模式(--independent) 独立模式下 Lerna 容许维护人员独立地的迭代各个包版本。每次发布时,你都会收到每一个发生更改的包的提示,同时来指定它是 patchminormajor 仍是自定义类型的迭代。

在独立模式下,lerna.json 文件中 version 属性的值将被忽略。

安装依赖

为全部项目安装依赖,相似于 npm i

$ lerna bootstrap
复制代码

当执行完上面的命令后,会发生如下的行为:

  1. 在各个模块中执行 npm install 安装全部依赖
  2. 将全部相互依赖的 Lerna 模块 连接在一块儿
  3. 在安装好依赖的全部模块中执行 npm run prepublish
  4. 在安装好依赖的全部模块中执行 npm run prepare

为packages文件夹下的package安装依赖

$ lerna add <package>[@version] [--dev] [--exact] # 命令签名

当咱们执行此命令后,将会执行下面那2个动做:

- 在每个符合要求的模块里安装指明的依赖包,相似于在指定模块文件夹中执行 `npm install <package>`。
- 更新每一个安装了该依赖包的模块中的 `package.json` 中的依赖包信息

# 例如
$ lerna add module-1 --scope=module-2 # 将 module-1 安装到 module-2
$ lerna add module-1 --scope=module-2 --dev # 将 module-1 安装到 module-2 的 devDependencies 下
$ lerna add module-1 # 将 module-1 安装到除 module-1 之外的全部模块
$ lerna add babel-core # 将 babel-core 安装到全部模块
复制代码

卸载依赖

$ lerna exec -- <command> [..args] # 在全部包中运行该命令

# 例如
$ lerna exec --scope=npm-list  yarn remove listr # 将 npm-list 包下的 listr 卸载
$ lerna exec -- yarn remove listr # 将全部包下的 listr 卸载
复制代码

清理依赖包

能够经过 clean 命令来快速删除全部模块中的 node_modules 文件夹。基本命令以下:

$ lerna clean
复制代码

检测模块是否发生过变动

$ lerna updated
# 或
$ lerna diff
复制代码

建立模块

Lerna 提供了两种建立或导入模块的方式,分别是 createimport

create

建立一个 lerna 管理的模块。基本命令格式以下:

$ lerna create <name> [loc]
复制代码

name 是模块的名称(必填项,可包含做用域,如 @uedlinker/module-a),必须惟一且能够发布(npm 仓库中无重名已发布包)

loc 是自定义的包路径(选填), 会根据你在 lerna.json 文件中的 packages 的值去匹配,默认采用该数组的第一个路径,指定其余路径时只要写明路径中的惟一值便可,例如想选择 /user/lerna-repo/modules 这个路径,只须要执行以下命令便可

命令执行完后,lerna 会帮咱们在指定位置建立模块的文件夹,同时会默认在该文件夹下执行 npm init 的命令,在终端上根据根据提示填写全部信息后会帮咱们建立对应的 package.json 文件,大体的结构以下

lerna-repo/
    ┣━ packages/
    ┃     ┗━ package-a/
    ┃            ┣━ ...
    ┃            ┗━ package.json
    ┣━ lerna.json
    ┗━ package.json
复制代码

import

导入一个已存在的模块,同时保留以前的提交记录,方便将其余正在维护的项目合并到一块儿。基本命令格式以下:

$ lerna import <dir>
复制代码

dir 是本项目外的包含 npm 包的 git 仓库路径(相对于本项目根路径的相对路径)

执行后会将该模块总体复制到指定的依赖包存放路径下,同时会把该模块以前全部提交记录合并到当前项目提交记录中

查看模块列表

建立完毕以后,咱们能够经过 list 命令来查看和确认如今管理的包是否符合咱们的预期,执行以下命令:

$ lerna list
复制代码

运行 script 脚本

lerna run 运行 npm script,能够指定具体的 package。

$ lerna run <script> -- [..args] # 在全部包下运行指定

# 例如
$ lerna run test # 运行全部包的 test 命令
$ lerna run build # 运行全部包的 build 命令
$ lerna run --parallel watch # 观看全部包并在更改时发报,流式处理前缀输出

$ lerna run --scope my-component test # 运行 my-component 模块下的 test
复制代码

版本迭代

lerna 经过 version 命令来为各个模块进行版本迭代。基本命令以下:

$ lerna version [major | minor | patch | premajor | preminor | prepatch | prerelease]
复制代码

若是不选择这次迭代类型,则会进入交互式的提示流程来肯定这次迭代类型

例如:

$ lerna version 1.0.1 # 按照指定版本进行迭代
$ lerna version patch # 根据 semver 迭代版本号最后一位
$ lerna version       # 进入交互流程选择迭代类型 
复制代码

注意: 若是你的 lerna 项目中各个模块版本不是按照同一个版本号维护(即建立时选择 independent 模式),那么会分别对各个包进行版本迭代

当执行此命令时,会发生以下行为:

  1. 标记每个从上次打过 tag 发布后产生更新的包
  2. 提示选择这次迭代的新版本号
  3. 修改 package.json 中的 version 值来反映这次更新
  4. 提交记录这次更新并打 tag
  5. 推送到远端仓库

小技巧: 你能够在执行此命令的时候加上 ——no-push 来阻止默认的推送行为,在你检查确认没有错误后再执行 git push 推送

--conventional-changelog

$ lerna version --conventional-commits
复制代码

version 支持根据符合规范的提交记录在每一个模块中自动建立和更新 CHANGELOG.md 文件,同时还会根据提交记录来肯定这次迭代的类型。只须要在执行命令的时候带上 --conventional-changelog 参数便可

--changelog-preset

$ lerna version --conventional-commits --changelog-preset angular-bitbucket
复制代码

changelog 默认的预设是 angular,你能够经过这个参数来选择你想要的预设建立和更新 CHANGELOG.md

预设的名字在解析的时候会被增添 conventional-changelog- 前缀,若是你设置的是 angular,那么实际加载预设的时候会去找 conventional-changelog-angular 这个包,若是是带域的包,则须要按照 @scope/name 的规则去指明,最后会被解析成 @scope/conventional-changelog-name

小技巧: 上述 2 个参数也能够直接写在 lerna.json 文件中,这样每次执行 lerna version 命令的时候就会默认采用上面的 2 个参数

"command": {
  "version": {
    "conventionalCommits": true,
    "changelogPreset": "angular"
  }
}
复制代码

发布

在一切准备就绪后,咱们能够经过 publish 命令实现一键发布多个模块。基本命令以下:

$ lerna publish
复制代码

当执行此命令时,会发生以下行为:

  1. 发布自上次发布以来更新的包(在底层执行了 lerna version,2.x 版本遗留的行为)
  2. 发布当前提交中打了 tag 的包
  3. 发布在以前的提交中更新的未经版本化的 “canary” 版本的软件包(及其依赖项)

注意: Lerna 不会发布在 package.json 中将 private 属性设置为 true 的模块,若是要发布带域的包,你还须要在 'package.json' 中设置以下内容:

"publishConfig": {
    "access": "public"
  }
复制代码

若是以前已执行过 lerna version 命令,这里若是直接执行 lerna publish 会提示没有发现有更新的包须要更新,咱们能够经过从远端的 git 仓库来发布:

lerna publish from-git
复制代码

lerna.json 解析

{
  "version": "1.1.3",
  "npmClient": "npm",
  "command": {
    "publish": {
      "ignoreChanges": [
        "ignored-file",
        "*.md"
      ]
    },
    "bootstrap": {
      "ignore": "component-*",
      "npmClientArgs": ["--no-package-lock"]      
    }
  },
  "packages": ["packages/*"]
}
复制代码

version:当前库的版本 npmClient: 容许指定命令使用的client, 默认是 npm, 能够设置成 yarn command.publish.ignoreChanges:能够指定那些目录或者文件的变动不会被publish command.bootstrap.ignore:指定不受 bootstrap 命令影响的包 command.bootstrap.npmClientArgs:指定默认传给 lerna bootstrap 命令的参数 command.bootstrap.scope:指定那些包会受 lerna bootstrap 命令影响 packages:指定包所在的目录

适用场景

最后咱们来讲说 Monorepo 的适用场景

  • 不过度庞大的项目,整合到一块儿有 100G 源码的话,仍是再考虑一下吧
  • 多模块 / 插件化项目,把官方维护的插件都做为 package 很是合适

另外,还须要:

  • 基础建设
  • 团队信任

基础建设是指强大的构建工具,能知足全部模块的 build 需求(纯前端项目的话,build 压力不大)

Monorepo 环境下,而且鼓励改别人的代码,一方面须要持续集成机制(例如 React – CircleCI)确认修改带来的影响,另外一方面还须要不一样团队之间互相信任。

参考

sosout.github.io/2018/07/21/… www.uedlinker.com/2018/08/17/… juejin.im/post/5d4aa8…

相关文章
相关标签/搜索