一.定位
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
命令处理依赖过后,会在moduleA
的node_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的大致过程是:
- 本地打个tag(例如
git tag v1.0.0
) - 自动更新依赖项版本号 示例
- 然后把各个package发布到npm
- 最后把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
添加对应配置项:
"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
里):
export GITHUB_AUTH="..."
配置完毕。要达到“自动”,前提是日常开发维护遵守约定的规范,否则最后工具肯定猜不出来changelog。规范是指:
- (建议)commit message关联上对应的issue
- (必须)创建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
:
## 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