在上一篇文章中介绍完了 lerna version
的运行机制后,那么在本篇文章中我将继续介绍一下 lerna 发包机制中最关键的一个 command 即 lerna publish
。
现在我们来继续介绍 lerna publish
运行机制,作为发包机制中的最后决定性的一个指令,lerna publish 的做的工作其实很简单,就是将 monorepo 需要发布的包,发布到 npm registry 上面去。
同样 lerna publish 也分为几种不同的场景去运行:
官方文档,lerna publish
一共有这样几种执行表现形式:
- 发布自上次发布来有更新的包(这里的上次发布也是基于上次执行
lerna publish
而言) - 发布在当前 commit 上打上了 annotated tag 的包(即
lerna publish from-git
) - 发布在最近 commit 中修改了 package.json 中的 version (且该 version 在 registry 中没有发布过)的包(即
lerna publish from-package
) - 发布在上一次提交中更新了的 unversioned 的测试版本的包(以及依赖了的包)
lerna publish
本身提供了不少的 options,例如支持发布测试版本的包即 (lerna version --canary
)。
在上文 lerna version 源码解析中,我们按照 configureProperties -> initialize -> execute
的顺序讲解了 lerna version 的执行顺序,其实在 lerna 中,几乎所有子命令源码的执行顺序都是按照这样一个结构在进行,lerna 本身作为一个 monorepo,主要是使用 core 核心中的执行机制来去分发命令给各个子项目去执行,因此套路都是一样的。
在开始阅读之前,我先提供一个整体的思维导图,可以让读者在开始阅读前有个大致的结构,也便于在阅读过程可以借此来进行回顾:

设置属性(configureProperties)
相比较于 lerna version,lerna publish
的这一步就简单许多,大致就是根据 cli 的 options 对一些参数进行了初始化:
通过注释就可以比较清晰的看到一些 options 以及相关参数的初始化,这里就不详细介绍。
初始化(initialize)
下面直接进来初始化的流程中来,因为涉及到发包相关的流程,这一步的前面过程涉及到的就是一些关于 npm 相关的 config 初始化,之后再根据不同的发包情况去进行对应的事件注册,这一步的事件注册以及执行方式都和 lerna version
源码解析时比较类似,主要过程可以分为三个步骤:
- 初始化 npm config 参数
- 根据不同的发包情况执行不同的方法
- 处理上一步返回的结果
这里不同的发包情况指的即是在文章开头介绍的 lerna publish 的几种执行方式,这里大致梳理一下以下的步骤:
initialize
前面有介绍主要分为三个步骤来执行,因此 1、3 两个步骤根据注释来理解过程还是比较清晰的,这里主要介绍一下第二步即 根据不同的发包情况来执行不用的方法,具体代码:
首先根据上面代码中以及文章开头介绍,可以很清晰的知道具体分为这几种情况:
from-git
即根据git commit
上的annotaed tag
进行发包from-package
即根据 lerna 下的 package 里面的 pkg.json 的 version 变动来发包--canary
发测试版本的包- 剩下不带参数的情况就直接走一个 bump version(即执行 lerna version)
下面从这几种情况做个介绍:
from-git
这一步的执行入口函数是 detectFromGit
,我们直接看这个函数的执行过程:
可以看到这一步函数的执行过程还是比较简单明了的,在上面注释中根据不同的方法执行过程分为了 5 个步骤。主要就是根据当前 commit 拿到 tags 里面的 packages 然后返回这些 packages 以及其版本信息。
from-package
这一步执行的入口函数是 detectFromPackage
,直接看执行过程:
这一步主要是在 getUnpublishedPackages
这一步筛选出需要更新的 packages,这里 lerna 作者使用了自己封装的 pacote 库来去做一些关于版本的比对,从而得到需要更新的 packages,这里有想了解的可以自行去阅读一下,不做过多赘述。
--canary
这一步执行的入口函数是 detectCanaryVersions
,直接看执行过程:
相比较于上面两步, --canary
的处理过程或许看上去要复杂一些,其实不然,根据上面代码注释中的内容可以比较清晰的看到整个执行流程,不过多了几种特殊情况需要去做一些判断,其中比较复杂的第三步,是需要通过 tag 得到一些相关的信息,需要更新的包,然后针对这些包现有的版本去做一些计算,可以参考上面的 makeVersion
方法,这里就根据 lerna 的 mode 分为了两种情况。
其中这里第二步还用到了在 lerna version 中收集变更的包的方法:collectUpdates
。具体的执行机制可以参考我的上一篇关于 lerna version 的文章。
bump version
如果不带参数的话,那么这一步就会直接执行一个 lerna version 的过程,一般 lerna publish 的预期行为是这样:
lerna version 的具体执行机制可以参考我的上一篇文章。
看完这几种情况之后再回到开头,再回顾一下 initialize 这一步最后对结果的一个处理过程,大致 initialize 的一个流程就这样结束了。
最后总结一下 lerna publish 的初始化过程,主要就是根据不同的发包情况,然后计算出需要发布的包的信息,例如包名称和更新版本。用于下一步发包的 execute 做准备。
执行(execute)
lerna publish
的最后一步即发包的过程就是在这里完成,代码结构为:
execute
是 lerna publish
的主要部分了,这一步的相对而言信息量比较巨大,我接下来会将上面的步骤拆一拆,一步一步来讲解 execute
这一步是怎么完成 lerna 发包的整个过程的。
首先可以看到上面代码中,我通过注释将这个步骤分成了六步:
1. 验证 npm && 项目license
首先上面可以看到,这一步分为两个方法,一步是做 npm 相关的验证:prepareRegistryActions
prepareRegistryActions
执行时会先去校验 registry,如果是第三方的 registry,会停止校验,用户在发包设置了 no-verify-access
就不进行后面校验,默认会校验。
校验过程是首先通过 getNpmUsername
去拿到用户的 username,这里是通过 npm 提供的相关接口来获取,具体流程可以自行参考。拿到 username 之后根据 username 以及本次 publish 中需要发布的包的信息去做一个鉴权,判断用户是否用该包的读写发包权限,没有就会抛错,最后一步是个 2fa 的验证,一般 npm 包都不会开启,主要是为了安全作用做二次验证使用,这里不做具体讲解。
下面在看 license 的校验过程,方法是 prepareLicenseActions
:
这一步并不会对主要流程有什么影响,主要就是找目前待发布的包中没有 license 的,然后给个 warnning 提示,这里找的方式使用过 lerna 自己构造的 project graph 去筛待发布包中不存在 liecense 文件的路径,想了解具体过程参考 getPackagesWithoutLicense
。
2. 更新本地依赖版本 && 待发布包 gitHead
可以你会对更新本地依赖版本这一步可能会有些迷惑,这里举个例子来解释一下,在 lerna 中,如果 workspaces 之前存在依赖的话,在这次发包中,例如 A 这个包依赖了 B,B 在这次发包中版本升级了,那么这里 A 里面依赖的 B 也要更新到对应的版本。
来看一下这一步:
这里涉及到的一些操作方法,都是来自于 lerna 构建的 project graph,这部分可以去参考一下 lerna core 中源码。
这里的 gitHead 是一个 hash 值,用户可以通过 --git-head 来自行指定,如果不指定的话,lerna 这里会默认帮你取当前 commit 的 hash 值,即通过 git rev-parse HEAD
来获取,一般 gitHead 结合 from-package
来使用,先看看代码:
在使用 from-package
的方式进行发包的时候,会把这个 githead 字段写在 package.json
里面。
3. 更新写入本地
这一步就是将第二步的一些更新直接写到 lerna 中对应项目里面去,即写到磁盘里面,主要的方法为:
这个 pkg.serialize()
方法,是可以在 lerna 的 core 中找到的,主要作用就是将相关的更新写入本地磁盘:
4. package pack
在讲解之前,我们得先知道 npm pack
这个操作是干什么的,它会打包当前的文件夹内容打包成一个 tar 包,我们在执行 npm publish 的时候会经常看到这个操作:

不过 npm publish 帮我们封装了这个过程,lerna publish 中也会有这个过程,这已经是发包前的最后一个操作了,具体可参考代码:
这一步首先可以参考 topoMapPackages
这个方法,他会按照拓扑顺序去对需要更新的包进行 pack,这里 publish 因为涉及到包之间的一些依赖关系,因此只能按照拓扑的顺序去执行,this.packagesToPublish
里面存的是待发布的包:
因此这里会按照拓扑顺序去对要发布的包进行打包成 tar 包的操作,具体执行方法是 packDirectory
这个方法,这个方法我只贴一下打包的那一段逻辑,还有一些其他的预处理逻辑做了一下删除:
5. Package publish
在上一步完成了待发布包的打包操作之后,这一步就是 lerna publish 整个流程的最后一步了!
这一步会将上一次打包的内容直接发布出去,先来看一下代码:
上一步讲了 topoMapPackages
这个方法,这里同样的,它会按照拓扑顺序去发布待发布的 pkg。
在 npmPublish
这个方法中,会将前面打包的 pkg 的 tar 包 publish 到 npm 上面去,这里用的是 lerna 作者自己的一个包,感兴趣的可以去 npm 上搜一下:@evocateur/libnpmpublish
这个包可以不用担心 tarball 打包自于哪个 pkg,只要你有个 tarball 它会帮你直接上传到 npm 上面去来完成一次发布,具体的内容可以在 npm 中找到。
这里因为引入了一些外部包加上这里有太多的边界条件处理,这里就不具体去看 npmPublish
这个方法了,贴上发布的那部分代码,可以参考一下:
那么再走到这一步结束之后,基本上整个 lerna 的发包流程都走完了。
后续的一些收尾工作的处理,可以再拉回 执行(execute) 这一节开头的代码分析那里。
总结
本文从源码角度剖析了一下 lerna publish 的执行机制,对于一些边界的 corner case 有些删减,按照主线讲解了 lerna publish 是怎么完成 lerna monorepo 中的整个发包流程操作的。希望本系列的文章能对你有所帮助。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!