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的更多相关文章

  1. 初探领域驱动设计(2)Repository在DDD中的应用

    概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...

  2. CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探

    CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码 ...

  3. 从273二手车的M站点初探js模块化编程

    前言 这几天在看273M站点时被他们的页面交互方式所吸引,他们的首页是采用三次加载+分页的方式.也就说分为大分页和小分页两种交互.大分页就是通过分页按钮来操作,小分页是通过下拉(向下滑动)时异步加载数 ...

  4. JavaScript学习(一) —— 环境搭建与JavaScript初探

    1.开发环境搭建 本系列教程的开发工具,我们采用HBuilder. 可以去网上下载最新的版本,然后解压一下就能直接用了.学习JavaScript,环境搭建是非常简单的,或者说,只要你有一个浏览器,一个 ...

  5. .NET文件并发与RabbitMQ(初探RabbitMQ)

    本文版权归博客园和作者吴双本人共同所有.欢迎转载,转载和爬虫请注明原文地址:http://www.cnblogs.com/tdws/p/5860668.html 想必MQ这两个字母对于各位前辈们和老司 ...

  6. React Native初探

    前言 很久之前就想研究React Native了,但是一直没有落地的机会,我一直认为一个技术要有落地的场景才有研究的意义,刚好最近迎来了新的APP,在可控的范围内,我们可以在上面做任何想做的事情. P ...

  7. 【手把手教你全文检索】Apache Lucene初探

    PS: 苦学一周全文检索,由原来的搜索小白,到初次涉猎,感觉每门技术都博大精深,其中精髓亦是不可一日而语.那小博猪就简单介绍一下这一周的学习历程,仅供各位程序猿们参考,这其中不涉及任何私密话题,因此也 ...

  8. Key/Value之王Memcached初探:三、Memcached解决Session的分布式存储场景的应用

    一.高可用的Session服务器场景简介 1.1 应用服务器的无状态特性 应用层服务器(这里一般指Web服务器)处理网站应用的业务逻辑,应用的一个最显著的特点是:应用的无状态性. PS:提到无状态特性 ...

  9. NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例

    一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象.消息被发送到队列中,“消息队列”是在消息的传输过程中保存消息的容器 ...

随机推荐

  1. 02 . 02 . Go之Gin+Vue开发一个线上外卖应用(集成第三方发送短信和xorm生成存储数据库表)

    集成第三方发送短信 介绍 用户登录 用户登录有两种方式: 短信登录,密码登录 短信登录是使用手机号和验证码进行登录 短信平台 很多云平台,比如阿里云,腾讯云,七牛云等云厂商,向程序开发者提供了短信验证 ...

  2. 已经编译安装的nginx/tenginx编译增加新模块

    只适用于自行编译安装的nginx配置 业务变更带来的Nginx增加模块需求 由于业务从php转为go开发,需要用到Http2的协议.这种协议在Nginx上需要http_v2_module这个模块的支持 ...

  3. PAT Saving James Bond - Easy Version

    Saving James Bond - Easy Version This time let us consider the situation in the movie "Live and ...

  4. Git的全局及单个仓库配置

    我们先来了解一下在git中的配置文件路径: /etc/gitconfig 文件: 包含系统上每一个用户及他们仓库的通用配置. 如果在执行 git config 时带上 --system 选项,那么它就 ...

  5. svg究竟是什么?

    svg究竟是什么? 1 要点 要点1:svg与jpg/png等格式的用途完全不同,不可相提并论,没有可比性,不可互相替代. 要点2:日常生活中,我们用相机拍摄自然景象得到的照片和视频,能且只能用jpg ...

  6. 模板——Fhq_treap

    $Fhq$ $treap$ #include <bits/stdc++.h> using namespace std; const int MAXN=100100; int n,root, ...

  7. 找出"吸血鬼数"(Java)

    吸血鬼数是指位数为偶数的数字,可以由一 对数字相乘而得到,而这对数字各包含乘积的一半 位数的数字,其中从最初的数字中选取的数字可以任意排序.以两个0结尾的数字是不允许的,例如,下列数字都是 " ...

  8. python super继承用法

    子类对父类的继承一般写法为1, 高级方法为super. 1 # 1,普通继承 2 #新建一个父类 3 class Father(): 4 def father(self,message): 5 pri ...

  9. 线程安全的SimpleDateFormat

    import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; imp ...

  10. Gromacs分子动力学模拟流程概述

    Gromacs分子动力学模拟主要可以分为以下几个步骤,不同的体系步骤可能略有不同. 在开始之前,先简单了解一下预平衡: 分子动力学模拟的最终目的是对体系进行抽样,然后计算体系的能量,各种化学键,成分分 ...