一、lerna是干什么用的
为什么叫lerna? lerna是希腊神兽九头蛇Lernaean Hydra的前几个字母,也间接形象的描述了多包管理的功能
简单来说:它优化了使用Git和NPM管理多包存储库的工作流,用来管理多个npm项目,处理每个项目相互之前引用关系,以及每个项目的发布管理。
一个lerna项目的结构大致如下:
my-lerna-repo/
package.json
packages/
package-1/
package.json
package-2/
package.json
二、什么时候使用lerna
假如我们有A、B、C三个npm项目,其中A要引用B,B要引用C,且A、B、C都可以单独被其他引用。这里考虑两个问题:
1、如何分别调试这几个项目呢?
2、这几个项目怎么发布才合理呢?
我们的答案可能是:
1、分别创建A、B、C三个项目,调试的时候使用npm link
2、每个项目分别发布,修改后,每个项目修改版本、打tag、依次push并publish
这样做确实可以打到目的,但是显然是个繁琐复杂而且容易出错的过程
首先,这样做需要每个开发者在本地link项目调试,耗时
其次,每次修改都要多个项目分别改版本、打tag、依次push并publish,耗力
这个时候就是lerna上场的时候了
三、如何使用lerna
npm install lerna -g
从git仓库clone下来新建的项目目录如下:
my-lerna-demo
|--- .gitignore
|--- README.md
在根目录中执行命令lerna init就可以初始化项目了,此时目录如下:
my-lerna-demo
|--- packages # 包目录
|--- .gitignore
|--- lerna.json #lerna配置
|--- package.json
|--- README.md
在packages下添加自己的项目,也可以使用lerna create创建
my-lerna-repo/
package.json
packages/
packageA/
index.js
package.json
packageB/
index.js
package.json
更多配置见: GitHub - lerna/lerna: A tool for managing JavaScript projects with multiple packages.
执行lerna bootstrap,它做了下面四件事:
(1)intsall所有包的依赖
(2)执行所有包的link操作
(3)执行每个包的npm run prepublish命令
(4)执行每个包的npm run prepare命令
5、发布到npm:lerna publish
lerna publish做了一套npm标准的发布流程管理。
1、根据git提交记录对比包的变化,发布发生修改的包
2、发布版本号的规范提示
3、每个版本自动生成git tag,并提交master
四、lerna publish 及管理发布版本的两种方式
lerna publish工作流程
(1)lerna publish
发布自上次发布以来发生变化的包,执行lerna version + lerna publish from-git
(2)lerna publish from-git
发布当前 commit 中打上 tag version 的包
(3)lerna publish from-packages
发布 package 中 pkg.json 上的 version 在 registry(高于 latest version)不存在的包
下面是lerna publish的执行逻辑图解,以及存在的问题
从上面这个图可以看到其中会有一些坑:
坑1:分支3的情况,因为开发者自己打的一些标签会影响lerna查找变更,可能会造成一些变更的包没有发布
解决办法:
(1)尽量避免自己打Tag
(2)或者只在一个专门的分支上,例如master,专门运行 lerna publish进行发布,这个分支不能自己打其他Tag
坑2:几条分支同时进行的情况,可能生成了相同的版本号,从而发生版本冲突
解决办法:
(1)分支开发者之间约定好各自版本号
(2)或者只在一个专门的分支上,例如master,专门运行lerna publish进行发布
坑3:运行lerna publish如果中途有包发布失败,再运行lerna publish的时候,因为Tag已经打上去了,所以不会再重新发布包到NPM
解决办法:
(1)运行lerna publish from-git,会把当前标签中涉及的NPM包再发布一次,PS:不会再更新package.json,只是执行npm publish
(2)运行lerna publish from-package,会把当前所有本地包中的package.json和远端NPM比对,如果是NPM上不存在的包版本,都执行一次npm publish
lerna版本管理的两种模式
分为固定版本和独立版本,即lerna.json的version字段配置
1、固定版本管理,version为指定版本号,如果想自动将所有包版本绑定在一起时使用此方式。lerna.json的version是最新发布的版本号。
每次publish会检查有修改的包,先修改lerna.json的version,下次发布会在此基础上递增。
注意:
(1)有相互引用的情况下,被引用的的包修改,引用的包的版本号会一同变更
(2)如果主版本为零,并选择任何非预发布版本号,将导致为所有包发布新版本,即使自上次发布以来并非所有包都已更改
2、独立版本管理,version配置为字符 'independent', lerna.json配置如下:
每次publish会检查有修改的包,然后对依次对每个package的version选择更新
注意:
(1)有相互引用的情况下,被引用的的包修改,引用的包也会被列入预发布的列表
五、公共依赖提升lerna bootstrap --hoist 与 lerna + yarn workspace
当你管理的多个工程下面如果很多依赖重复安装,这就很影响效率,lerna提供了依赖提升的功能,lerna bootstrap --hoist。
1、只需要在lerna bootstrap后加上 --hoist, 或者在lerna.json的command配置hoist参数,就可以使用这个功能,但使用时需要注意以下几点:
(1)当不同工程的相同依赖项,不同版本是怎么处理的:lerna会对比版本占比多的依赖项提取到根目录,如果版本都不同,按顺序取最后一个包的版本提升,其他版本在各自工程下安装。
(2)node的模块规范是不断向上找node_modules寻找依赖,但如果你的代码规范不是commonjs的引入方式,可能会找不到依赖,这里需要你自己规范你的引用方式。
(3)如果你在某个工程引用了不属于当前工程的依赖项,但是其他工程有这个依赖项,你一样可以从提升的node_modules里获取到, 并不会报错,但是你可能不知道你的依赖项并没有在package.json中,这里可以使用eslint-plugin-import检查所有新导入是否来自 package.json
2、最优解 lerna + yarn workspace
lerna默认是在npm的基础上做的依赖提升,但现如今大多数工程都是使用yarn来管理, yarn的优势这里不多赘述(了解更多npm和yarn的对比),如果你的包管理方式是yarn,那么就可以使用yarn workspace来代替lerna 的hoist,所以用lerna结合yarn workspace,即为目前多工程管理的最优解。
事实上yarn workspace和lerna bootstrap --hoist做的事情大致是一样的,只不过yarn workspace的实现可以调用yarn的所有能力,相对lerna的实现更底层也更简易,yarn也是受到了lerna的启发才衍生了yarn workspace,
后面lerna也配合yarn提供了yarn workspace的配置,将依赖管理全权交给yarn,
lerna配合yarn workspace的PR(大佬的code review值得学习😄)
这里看完PR就很清楚了,其实就是:
(1)lerna在lerna.json里增加了useWorkspaces参数
(2)useWorkspaces为true时,执行lerna bootstrap会检查你的根package.json是否配置了workspace和private,未配置会报错提示,否则就相当于执行了yarn,将依赖管理和软连接的建立完全交给yarn。
具体使用就是在根目录的package.json下添加:
在lerna.json里配置:
这样就可以达到依赖管理全部用yarn,发布版本和测试部署用lerna,实际上你可以直接使用yarn worksapce而不用lerna bootstrap,但是bootstrap后面也还是做了一些事情的,所以看个人需要。
六、lerna的一些常用命令
1、lerna bootstrap
初始化项目依赖以及建立link关系以及预发布处理
2、lerna publish
lerna publish做了一套npm标准的发布流程管理
3、lerna changed
用来查看发生改变的package,即预发布的包
4、lerna clean
用来清空所有package的node_moudles依赖
5、lerna run
用来执行每个package下的scripts命令
6、lerna add
用来给package添加依赖,可以全部添加,也可以指定添加
有兴趣了解更多命令在lerna github。