lerna入门指南

2019-06-12 14:24:21 浏览数 (1)

一.定位

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

多模块管理工具,用来帮助维护monorepo

P.S.Lerna是Babel自己日用并开源的工具,见Why is Babel a monorepo?

二.monorepo

monorepo(monolithic repository),与multirepo相对,分别是单代码仓库与多代码仓库(one-repository-per-module)

multirepo即传统做法,按模块分为多个代码库,实践中发现一些问题:

  • issue管理混乱,经常有在core repo提module问题的,需要Close this and track that
  • changelog难以整合,需要人工梳理所有变动的仓库,并做整合
  • core repo版本更新麻烦,需要同步所有module更新其依赖的core repo版本

monorepo把所有相关module都放到一个repo里,每个module独立发布,但使用与该repo统一的版本号(例如Babel和React),issue和PR都集中到该repo,changelog可以简单地从一份commit列表梳理出来(甚至如果按照commit规范关联issue tag的话,能够自动生成规范的changelog)

monorepo也存在一些问题,但不如上面提到的痛点强烈:

  • repo体积较大,可能带来版本控制的问题(Git不适合管理体积太大的repo)
  • 统一构建工具,对构建工具提出了更高要求,要能构建各种相关module

从源码管理的角度来看,multirepo与monorepo是两种不同的理念,前者允许多元化发展,各个module可以有自己的玩法(构建,依赖管理,单元测试等),后者希望集中管理,减少玩法差异带来的沟通成本

monorepo标志性的特征是目录结构,例如React:

代码语言:javascript复制
react-16.2.0/
 packages/
   react/
   react-art/
   react-.../

每个module都有自己的依赖项(package.json),能够作为独立的npm package发布,只是源码放在一起维护

典型案例:

  • rollup:multirepo
  • babel:monorepo

P.S.之前使用rollup遇到问题都先去主repo查相关issue,再根据线索找到对应的plugin repo,再查相关issue。一直感觉异常麻烦,又说不出来哪里不对,原来是源码组织方式带来的困扰

三.lerna试玩

代码语言:javascript复制
// 安装
npm install lerna -g
git init hoho-lerna && cd hoho-lerna
// 初始化目录结构
lerna init

得到如下结构:

代码语言:javascript复制
hoho-lerna/
 packages/
 lerna.json
 package.json

创建module:

代码语言:javascript复制
mkdir packages/hoho-lerna-core && cd packages/hoho-lerna-core
npm init

这样最终会得到一堆package:

代码语言:javascript复制
packages/
 hoho-lerna-core/
   package.json
 hoho-lerna-module-a/
   package.json
 hoho-lerna-module-b/
   package.json
 module.../

我们实际做的事情是按模块拆分成package,并(通过module级的package.json)声明了各package之间的依赖关系

依赖处理

如果moduleA依赖core,通过lerna bootstrap命令处理依赖过后,会在moduleAnode_modules下创建软链接指向core目录,有一只活生生的例子

注意:npm不会自动安装peerDependencies,lerna也不提供这个服务

lerna bootstrap按照之前声明的依赖关系,通过建立软链接来把各package实际关联起来

发布package

既然都放在packages里了,容易统一管理,所以支持一键发布所有package到npm

P.S.先要有npm账号(自行注册),并npm adduser添加到本地配置里

准备好之后,迫不及待的开始一箭n星

代码语言:javascript复制
lerna publish

不出意外的话,会得到类似输出:

代码语言:javascript复制
lerna info version 2.7.0
lerna info current version 0.0.0
lerna info Checking for updated packages...
lerna info Comparing with initial commit.
lerna info Checking for prereleased packages...
? Select a new version (currently 0.0.0) Major (1.0.0)Changes:
- hoho-lerna-core: 1.0.0 => 1.0.0
- hoho-lerna-module-a: 1.0.0 => 1.0.0
- hoho-lerna-module-b: 1.0.0 => 1.0.0? Are you sure you want to publish the above changes? Yes
lerna info publish Publishing packages to npm...
lerna info published hoho-lerna-module-b
lerna info published hoho-lerna-core
lerna info published hoho-lerna-module-a
lerna info git Pushing tags...
Successfully published:
- hoho-lerna-core@1.0.0
- hoho-lerna-module-a@1.0.0
- hoho-lerna-module-b@1.0.0
lerna success publish finished

然后,npm registry里就多了3个垃圾package……

publish的大致过程是:

  1. 本地打个tag(例如git tag v1.0.0
  2. 自动更新依赖项版本号 示例
  3. 然后把各个package发布到npm
  4. 最后把tag和相应的commit给push上去

注意:如果发布到npm这一步失败了的话(比如没配置npm账号),下一次直接lerna publish无法直接发布,貌似因为本地tag已经是v1.0.0认为上次发布成功了。把这个tag手动滚掉也不行,.git里可能记了一些发布状态,滚掉之后出现commit hash匹配错误,这里不太友好

P.S.更多命令请查看Lerna

自动生成changelog

先安装changelog工具:

代码语言:javascript复制
npm install lerna-changelog -g

然后在lerna.json添加对应配置项:

代码语言:javascript复制
"changelog": {
 "repo": "ayqy/hoho-lerna",
 "labels": {
   "enhancement": ":rocket: Enhancement",
   "bug": ":bug: Bug Fix",
   "doc": "Refine Doc",
   "feat": "New Feature"
 },
 "cacheDir": ".changelog"
}

特别注意repo是必填的,说是能自动推断,实际上不太靠谱,见The ‘repo’ field automatically inferred failed, but no error occurred

P.S.labels里,key是要在Github配置的标签,用来给Issue/PR分类,value里的:bug:只是调皮的emoji,会作为changelog里该类change的标题

到这里还不算完,还需要Github repo权限(为了能查Issue、PR),把token以环境变量的形式暴露出来(常用的话,可以添到~/.bash_profile里):

代码语言:javascript复制
export GITHUB_AUTH="..."

配置完毕。要达到“自动”,前提是日常开发维护遵守约定的规范,否则最后工具肯定猜不出来changelog。规范是指:

  1. (建议)commit message关联上对应的issue
  2. (必须)创建PR时要选择我们预定义的label

因为工具只整理github带有指定label的PR,并把commit message作为changelog项,建议commit message里关联上issue,生成的changelog就能关联到对应issue:

Uses github PR/Issue names categorized by labels with configurable headings.

例如:

代码语言:javascript复制
git cm -m "feat: changelog, Close #1"

然后提交PR并给贴上label:feat,merge之后,本地pull过来试试lerna-changelog

代码语言:javascript复制
## Unreleased (2018-01-13)#### New Feature
* [#2](https://github.com/ayqy/hoho-lerna/pull/2) feat: changelog, Closes [#1](https://github.com/ayqy/hoho-lerna/issues/1). ([@ayqy](https://github.com/ayqy))#### Committers: 1
- 黯羽轻扬 ([ayqy](https://github.com/ayqy))

相当漂亮:https://github.com/ayqy/hoho-lerna/releases/tag/v1.1.0

P.S.应该在.gitignore忽略掉本地生成的changelog临时文件,仅在发布新版本时本地lerna-changelog,并把生成的changelog贴到release note。不自动发布release note可能是API限制或出于慎重考虑,毕竟release note还是比较重要的

另外,以这种方式自动整理出changelog,实际上靠的是开发中约束(PR的label规范,commit message作为changelog项的规范),与lerna没有太大关系,只要是monorepo(Issue/PR)都放在一起,就可以按照这个思路获取Issue/PR信息,整理出changelog

相当于把最后梳理changelog的巨大工作量分布到日常开发维护了,change都要走PR,而且要有issue记录,不习惯的话还是很麻烦的(有要求commit message自带label而不走PR的呼声,以后应该会支持)

四.适用场景

哪些场景可以采用monorepo(并用lerna管理?)?

  • 不过分庞大的项目,整合到一起有100G源码的话,还是再考虑一下吧
  • 多模块/插件化项目,把官方维护的插件都作为package非常合适

另外,还需要:

  • 基础建设
  • 团队信任

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

monorepo环境下,可以并且鼓励改别人的代码,一方面需要持续集成机制(例如React – CircleCI)确认修改带来的影响,另一方面还需要不同团队之间互相信任,否则会经常出现一个团队的变更影响了另一个团队的情况,需要回滚掉别人的修改,反而影响效率

P.S.Lerna出来很久了(和Babel差不多年纪),很多项目都在用了

参考资料

  • Lerna:很简练的官方文档
  • monorepo 新浪潮 | introduce lerna:前辈的helloworld还不错
  • REPO 风格之争:MONO VS MULTI
  • Mono Repository Tool Comparison:monorepo工具对比
  • New wave modularity with Lerna, monorepos, and npm organizations

0 人点赞