初探Lerna
1、简介
首先是关于Monorepo(一篇不错的介绍Monorepo的文章),它是管理项目代码的一种方式,主要手段是通过在一个项目仓库中管理多个模块/仓库包。而Multirepo是传统的仓库管理方法,也是公司目前所用的方法,即所有的项目包都是独立仓库部署和管理。两种方式对比如下:

两种方式进行对比的话,千人千面。前者允许多元化发展(各项目可以有自己的构建工具、依赖管理策略、单元测试方法),后者希望集中管理,减少项目间的差异带来的沟通成本。
虽然拆分子仓库、拆分子 npm 包是进行项目隔离的天然方案,但当仓库内容出现关联时,没有任何一种调试方式比源码放在一起更高效。
在前端开发环境中,多 Git Repo,多 npm 则是这个理想的阻力,它们导致复用要关心版本号,调试需要 npm link。而这些是 MonoRepo 最大的优势。
上图中提到的利用相关工具就是今天的主角 Lerna ! Lerna是业界知名度最高的 Monorepo 管理工具,功能完整。而且目前很多大型开源项目都采用了这种方式,比如Babel、React、Meteor、Ember、Jest、Vue等等,当我们查看他们的源码时,可以发现他们的目录都是将主要内容放在packages目录中,分多个模块进行管理。
2、使用
2.1、全局安装Lerna
npm install lerna -g
或者
yarn add lerna -D
安装完成后,在控制台输入lerna -v,只要可以显示版本号,就意味着我们安装成功了。控制台输出如下图:

2.2、初始化
初始化之前的步骤无非两种,第一种是创建一个lerna-repo目录,第二种是在git上创建一个项目lerna-repo并clone到本地,然后本地进入该目录并执行
npx lerna init
一开始我们都是使用lerna的默认模式我们的目录结构会变成如下:

顾名思义,lerna.json就是Lerna的配置文件,里面可以进行不同的业务或者不同的场景的配置话定义,我们可以查看初始化的lerna.json的文件,如下图:

首先就是packages属性,它的类型是一个数组,在这里可以配置可以发布的npm包的目录,可以根据不同的场景进行定义发布的npm包所在的不同的文件夹。其次的属性version就是当前lerna项目的版本号。
2.3、引入npm包/生成一个npm包
这里不得不介绍一下引入npm包的强大之处,它可以把你引入的包的git commit记录完整的引入。我们一般都是先将要引入的包下载到本地,命令如下:
lerna import ../userlogin
如果是新项目,需要新生成一个npm包的话,可以使用如下命令lerna create <包名> [目录]:
lerna create base packages/npm
ps:此处npm为原有目录;
引入一个npm包其实还可以通过进入当前packages文件夹下,通过git clone的方式进行引入,这种优点是可以自由的在某一个项目中进行切换分支,如果是import的方式引入的npm包,目前我是没找到自由切换单项目分支的方式,欢迎大家补充。
create时,按照lerna的提示输入name、version等属性就好了,如果为了省事可以一路回车走到最后,它就会以默认值的形式生成一个新的npm包。
无论是引入的npm包,还是生成的npm包,默认目录都是在当前项目根目录下的packages文件夹下,生成的目录如下:

此处会有几种常见的错误展示,第一种就是由于你是新建的lerna项目,没有进行git commit,这时候进行lerna import会报错如下:
Error: Command failed: git rev-parse HEAD
lerna ERR! fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.
lerna ERR! Use '--' to separate paths from revisions, like this:
lerna ERR! 'git <command> [<revision>...] -- [<file>...]'
lerna ERR!
lerna ERR! HEAD
lerna ERR!
lerna ERR! at makeError (/usr/local/lib/node_modules/lerna/node_modules/execa/index.js:174:9)
lerna ERR! at Function.module.exports.sync (/usr/local/lib/node_modules/lerna/node_modules/execa/index.js:338:15)
lerna ERR! at Object.execSync (/usr/local/lib/node_modules/lerna/node_modules/@lerna/child-process/index.js:22:16)
lerna ERR! at ImportCommand.getCurrentSHA (/usr/local/lib/node_modules/lerna/node_modules/@lerna/import/index.js:129:34)
lerna ERR! at ImportCommand.initialize (/usr/local/lib/node_modules/lerna/node_modules/@lerna/import/index.js:98:31)
lerna ERR! at Promise.resolve.then (/usr/local/lib/node_modules/lerna/node_modules/@lerna/command/index.js:266:24)
lerna ERR! at <anonymous>
lerna ERR! lerna Command failed: git rev-parse HEAD
lerna ERR! lerna fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.
lerna ERR! lerna Use '--' to separate paths from revisions, like this:
lerna ERR! lerna 'git <command> [<revision>...] -- [<file>...]'
lerna ERR! lerna
lerna ERR! lerna HEAD
如果你没有初始化git项目的话,会有如下报错:
cli v3.8.0
lerna ERR! Error: Command failed: git log --format=%h
lerna ERR! fatal: Not a git repository (or any of the parent directories): .git
lerna ERR!
lerna ERR!
lerna ERR! at makeError (/usr/local/lib/node_modules/lerna/node_modules/execa/index.js:174:9)
lerna ERR! at Function.module.exports.sync (/usr/local/lib/node_modules/lerna/node_modules/execa/index.js:338:15)
lerna ERR! at Object.execSync (/usr/local/lib/node_modules/lerna/node_modules/@lerna/child-process/index.js:22:16)
lerna ERR! at ImportCommand.externalExecSync (/usr/local/lib/node_modules/lerna/node_modules/@lerna/import/index.js:137:34)
lerna ERR! at ImportCommand.initialize (/usr/local/lib/node_modules/lerna/node_modules/@lerna/import/index.js:82:25)
lerna ERR! at Promise.resolve.then (/usr/local/lib/node_modules/lerna/node_modules/@lerna/command/index.js:266:24)
lerna ERR! at <anonymous>
lerna ERR! lerna Command failed: git log --format=%h
lerna ERR! lerna fatal: Not a git repository (or any of the parent directories): .git
lerna ERR! lerna
但是如果原项目是具有合并提交冲突的存储库时,import命令便无法尝试应用所有提交。此时可以使用 --flatten命令标志来请求导入固定的历史记录,也就是将每次合并提交作为单独的更改,引入合并。报错如下:
lerna ERR! import Rolling back to previous HEAD (commit e232dec13e7a62943567257eff8573db2eb1a19e)
lerna ERR! EIMPORT Failed to apply commit 2aafdfd.
lerna ERR! EIMPORT Command failed: git am -3 --keep-non-patch
lerna ERR! EIMPORT Can't find Husky, skipping applypatch-msg hook
lerna ERR! EIMPORT You can reinstall it using 'npm install husky --save-dev' or delete this hook
lerna ERR! EIMPORT error: Failed to merge in the changes.
lerna ERR! EIMPORT hint: Use 'git am --show-current-patch' to see the failed patch
lerna ERR! EIMPORT Applying: feat(component): add chromeDownload component
lerna ERR! EIMPORT Using index info to reconstruct a base tree...
lerna ERR! EIMPORT M packages/dt-react-component/.storybook/config.js
lerna ERR! EIMPORT Falling back to patching base and 3-way merge...
lerna ERR! EIMPORT Auto-merging packages/dt-react-component/.storybook/config.js
lerna ERR! EIMPORT CONFLICT (content): Merge conflict in packages/dt-react-component/.storybook/config.js
lerna ERR! EIMPORT Patch failed at 0001 feat(component): add chromeDownload component
lerna ERR! EIMPORT When you have resolved this problem, run "git am --continue".
lerna ERR! EIMPORT If you prefer to skip this patch, run "git am --skip" instead.
lerna ERR! EIMPORT To restore the original branch and stop patching, run "git am --abort".
lerna ERR! EIMPORT
lerna ERR! EIMPORT
lerna ERR! EIMPORT You may try again with --flatten to import flat history.
命令如下:
$ lerna import ../base --flatten
2.4、显示当前列表
lerna list
2.5、 为项目包添加依赖
命令格式:
lerna add 包名 [--scope=特定的某个包]
例子:
lerna add component --scope=base
如上面例子代码,执行成功这段代码后,我们可以在base项目中引用component的包了,而且会自动把component包的版本号更新到base包的package.json中。此处需要注意,如果我们add的包不在我们的packages目录下,它就会自动从npm的仓库上进行下载,并且因为加了scope,所以只会给base包进行安装依赖,如果不加scope这段代码的话,会自动给packages目录下所有的包更新安装component依赖。如果原项目中在package.json中有当前npm包的配置,那么这行命令会将原配置删除,并新增当前最新版本的npm包,保证其的版本实时性。
2.6、为每个包安装依赖
lerna bootstrap --scope=特定的某个包
这行代码功能和npm install或者yarn差不多,如果不加后缀scope,lerna会自动把当前工程下的所有包的依赖都安装好。
请注意,Lerna在这有一个非常强大的功能,
2.7、删除某个包的依赖
lerna clean --scope=特定的某个包
同安装依赖,这行代码就是删除某个包的依赖,跟rm -rf node_modules功能一致,将某个项目的node_modules删除,如果不加scope,就会默认删除全部项目的依赖,如下图,会出现提示:
2.8、运行项目中的script命令
lerna run 命令 --scope=特定的某个包
这里可以运行start等约定好的script命令,同理,如果没有scope后缀,lerna会运行每个包的该script命令,截图如下:

2.9、查看可以发布的包
lerna changed
该命令是查看当前可以发布的包,显示自上次relase tag以来有修改的包,输入后展示图会与下图类似:

如图展示,当前有三个包可以进行发布,但是如果你发布完以后再执行该命令,此处就会展示No changed packages found。
2.10、发布某个项目
lerna publish --dist-tag=tag名
输入该命令后,会展示如下图:

在上图中,控制台会让用户选择要发布的版本的版本号,最后一个选项为自定义选项。--dist-tag=tag名为发布某一个分支的包,主要用于测试或者多版本并存的情况。发布模式如下图:

2.11、查看diff
lerna diff
作用类似git diff,主要用于显示自上次relase tag以来有修改的包的差异。
2.12、链接引用
lerna link
链接项目引用的库。主要用于项目包建立软链,类似 npm link。
2.13、exec
在每个包目录下执行任何命令。
$ lerna exec -- <command> [..args] # runs the command in all packages
$ lerna exec -- rm -rf ./node_modules
$ lerna exec -- protractor conf.js
$ lerna exec -- npm view \$LERNA_PACKAGE_NAME
$ lerna exec -- node \$LERNA_ROOT_PATH/scripts/some-script.js
3、模式
Lerna提供了两种管理项目的方式,一种是固定模式(Fixed mode),另一种是独立模式(Independent mode),两者区分如下:
固定模式下,packages文件夹下的所有包将会公用一个版本号version(这个version就是lerna.json中的version字段),会自动将所有的包绑定到一个版本号上面,当任意一个npm包发生了更新,整个公用版本号就会发生变化。
独立模式下,每个npm包都具有一个独立的版本号,在使用发布命令lerna publish命令时,可以为每个包单独的进行发布操作,同时只更新当前包的版本号,不会影响其余包的版本。在此模式下,lerna.json的version字段改为independent即可。
lerna version 会检测从上一个版本发布以来的变动,但有一些文件的提交,我们不希望触发版本的变动,譬如 .md 文件的修改,并没有实际引起 package 逻辑的变化,不应该触发版本的变更。可以通过ignoreChanges配置排除,如下:
{
"packages": [
"packages/*"
],
"command": {
"bootstrap": {
"hoist": true
},
"version": {
"conventionalCommits": true
}
},
"ignoreChanges": [
"**/*.md"
],
"version": "0.0.1-alpha.1"
}
效果如下:

初探Lerna的更多相关文章
- 初探领域驱动设计(2)Repository在DDD中的应用
概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...
- CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探
CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码 ...
- 从273二手车的M站点初探js模块化编程
前言 这几天在看273M站点时被他们的页面交互方式所吸引,他们的首页是采用三次加载+分页的方式.也就说分为大分页和小分页两种交互.大分页就是通过分页按钮来操作,小分页是通过下拉(向下滑动)时异步加载数 ...
- JavaScript学习(一) —— 环境搭建与JavaScript初探
1.开发环境搭建 本系列教程的开发工具,我们采用HBuilder. 可以去网上下载最新的版本,然后解压一下就能直接用了.学习JavaScript,环境搭建是非常简单的,或者说,只要你有一个浏览器,一个 ...
- .NET文件并发与RabbitMQ(初探RabbitMQ)
本文版权归博客园和作者吴双本人共同所有.欢迎转载,转载和爬虫请注明原文地址:http://www.cnblogs.com/tdws/p/5860668.html 想必MQ这两个字母对于各位前辈们和老司 ...
- React Native初探
前言 很久之前就想研究React Native了,但是一直没有落地的机会,我一直认为一个技术要有落地的场景才有研究的意义,刚好最近迎来了新的APP,在可控的范围内,我们可以在上面做任何想做的事情. P ...
- 【手把手教你全文检索】Apache Lucene初探
PS: 苦学一周全文检索,由原来的搜索小白,到初次涉猎,感觉每门技术都博大精深,其中精髓亦是不可一日而语.那小博猪就简单介绍一下这一周的学习历程,仅供各位程序猿们参考,这其中不涉及任何私密话题,因此也 ...
- Key/Value之王Memcached初探:三、Memcached解决Session的分布式存储场景的应用
一.高可用的Session服务器场景简介 1.1 应用服务器的无状态特性 应用层服务器(这里一般指Web服务器)处理网站应用的业务逻辑,应用的一个最显著的特点是:应用的无状态性. PS:提到无状态特性 ...
- NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例
一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象.消息被发送到队列中,“消息队列”是在消息的传输过程中保存消息的容器 ...
随机推荐
- Linux 系统编程 学习:11-线程:线程同步
Linux 系统编程 学习:11-线程:线程同步 背景 上一讲 我们介绍了线程的属性 有关设置.这一讲我们来看线程之间是如何同步的. 额外安装有关的man手册: sudo apt-get instal ...
- Netty源码解析 -- 事件循环机制实现原理
本文主要分享Netty中事件循环机制的实现. 源码分析基于Netty 4.1 EventLoop 前面分享服务端和客户端启动过程的文章中说过,Netty通过事件循环机制(EventLoop)处理IO事 ...
- 关于python递归函数,这样写就对了
大家好我是致力于让每个人都能够轻松学会编程的小梁,在这条路上任重道远,关注我,每天让您获取来自编程的乐趣. 关注公众号"轻松学编程".了解更多. 今天就给大家分享一下关于使用递归函 ...
- .NetCore HttpClient发送请求的时候为什么自动带上了一个RequestId头部?
奇怪的问题 最近在公司有个系统需要调用第三方的一个webservice.本来调用一个下很简单的事情,使用HttpClient构造一个SOAP请求发送出去拿到XML解析就是了. 可奇怪的是我们的请求在运 ...
- ERP的权限管理的操作与设计--开源软件诞生24
赤龙ERP用户与权限管理讲解--第24篇 用日志记录"开源软件"的诞生 [进入地址 点亮星星]----祈盼着一个鼓励 博主开源地址: 码云:https://gitee.com/re ...
- php 之根据mysql字段 批量生成 array 数组
ci框架 验证字段 需要 生成类似为: array('field' => 'admin_id','label' => '账号ID','rules' => 'integer'), ...
- day86:luffy:前端发送请求生成订单&结算页面优惠劵的实现
目录 1.前端发送请求生成订单 1.前端点击支付按钮生成订单 2.结算成功之后应该清除结算页面的数据 3.后端计算结算页面总原价格和总的真实价格并存到数据库订单表中 2.优惠劵 1.准备工作 2.前端 ...
- 压缩法备份Linux系统文件
在使用Ubuntu之前,相信很多人都有过使用Windows系统的经历.如果你备份过Windows系统,那么你一定记忆犹新:首先需要找到一个备份工 具(通常都是私有软件),然后重启电脑进入备份工具提供的 ...
- Netlink 内核实现分析 3
Netlink IPC 数据结构 #define NETLINK_ROUTE 0 /* Routing/device hook */ #define NETLINK_UNUSED 1 /* Unuse ...
- 在linux下,为什么 i386 ELF可执行文件默认从地址(.text)0x08048000开始分配。 而 x64是0x400000
