使用Lerna + Yarn Workspace管理Monorepo项目
1.前言
通常,我们会根据自身业务的实际情况,将通用的组件、逻辑等提取成NPM包,方便以后复用。但这些提取出来的NPM包可能互相之间存在依赖,如果仍然采用 Multirepo 的形式进行管理,则在包的版本管理、依赖管理、调试等诸多方面存在不便。
Monorepo 能很好的解决上述问题,为更加方便的使用 Monorepo 来管理我们的项目,我们需要一些趁手的工具,Lerna + Yarn Workspace 的组合就是这样一件优秀的工具。然而 Lerna 中途经历过较大的改动,其文档也不是那么容易看明白,中文材料大多使用较老版本的 Lerna,使用上和较新版本存在差异。
本文将介绍较新版本的 Lerna 如何结合 Yarn Workspace 管理 Monorepo 项目,后续操作在 Lerna V7 和 V8 中经过实践。
2.Lerna + Yarn Workspace
其实除了 Lerna + Yarn Workspace 的方案外,还有其他方案也可以帮助我们管理 Monorepo 项目,比如 pnpm 等,但我选择 Lerna + Yarn Workspace 一方面是由于习惯了使用 yarn,只要引入 Lerna 即可,另一方面公司的基建对 yarn 也有很好的支持,所以选择了 Lerna + Yarn Workspace。
在使用 Lerna + Yarn Workspace 时,两者的分工有所不同:
- Yarn 负责依赖管理,在 install 时将子包相同的依赖安装到根目录下的 node_modules 中,避免了在子包中重复安装。同时 Yarn 也可以处理子包之间的相互依赖关系,使用软链的方式连接各个子包。
- Lerna 则负责子包的发布,包括版本号更新、依赖更新、发布等。
3.入门实践
3.1.初始化
3.1.1.项目初始化
首先你需要初始化 Monorepo 项目,新建一个目录 my-monorepo-project,进入到目录中执行:
yarn init
这一步完成后,项目根目录下会出现 package.json,这里需要将 private 字段设为 true。
3.1.2.Workspaces 初始化
接下来进行 Workspaces 初始化,在根目录新建 packages 文件夹,并在 packages.json 中新增 workspaces 配置:
"workspaces": ["packages/*"
]
workspaces 字段定义了一个包含多个路径的数组,这些路径指向了项目中的各个子包的位置。如上配置就是将 packages 目录下的所有项目都看做是子包。
这一步完成之后,就可以在根目录下执行 yarn 安装依赖了,假设我们的项目目录如下:
/my-monorepo-project
|-- packages
| |-- package1
| |-- package2
| |-- package3
|-- package.json
|-- yarn.lock
其中 package1 依赖了 package2,则 yarn 之后,my-monorepo-project 的根目录下会出现 node_modules 文件夹,在 node_modules 查看各个子包对应的目录,会发现它们是以软链的形式链接到各自的源码目录中。
值得注意的是,此时就算 package1 的 package.json 中没有声明对 package2 的依赖,但也可以直接在 package1 中引用 package2 了。但当我们发布后,由于 package1 没有声明对 package2 的依赖,用户在安装 package1 时不会同时安装 package2,就会造成运行异常。正确的做法仍然需要在 package1 的 package.json 中声明对 package2 的依赖,不过不要通过 yarn workspace package1 add package2
的方式添加依赖,这样 package2 会从 npm 上下载,而不是链接到本地源码。
3.1.3.Lerna初始化
现在就可以引入 Lerna 了,在根目录中继续执行:
yarn add lerna -D
npx lerna init
如此一来,在根目录下就会新增 lerna.json 文件,其内容是:
{"$schema": "node_modules/lerna/schemas/lerna-schema.json","version": "independent","npmClient": "yarn"
}
在较早版本的 Lerna 生成的 lerna.json 文件中,还需要设置 useWorkspaces 字段,不过Lerna 7 和 8 中已经不用设置该字段了。
另外需要注意 version 字段的值。version 可以设置为具体的版本号,比如 0.0.1,此时 Lerna 将采用固定模式(Fixed/Locked mode)管理子包版本,即有的子包将共享同一个版本号。Lerna 默认采用该方式,version 初始值为 0.0.0。
也可以将 version 设置为 independent,表示每个子包都可以独立地管理自己的版本号(Independent mode),当发布时 Lerna 会让你为子包选择版本号。由于我是将已有的项目改造为 Monorepo 项目,各子包已经独立发版很长时间了,所以将 version 改成了 independent。
3.2.开发
完成初始化后,我们就可以开发我们的子包了。在开发过程中,我们可能需要添加第三方依赖,也有可能需要运行子包的 scripts 脚本。我们来看看如何执行这些常见的操作。
3.2.1.添加依赖
如果需要添加第三方依赖,可以将依赖添加到 workspaces 根目录:
# 在任何目录执行
yarn add <依赖名> -W# 或者在根目录中执行
yarn add <依赖名>
这样依赖会被安装到根目录的 node_modules 中,所有子包都可以引用。
如果需要为某个子包单独添加某个依赖,可以执行:
# 在任何目录执行
yarn workspace <子包名> add <依赖名># 或者在进入子包对应的目录中执行
yarn add <依赖名>
这样依赖只会被会被安装到子包对应目录下的 node_modules 中,其他子包不能直接引用。注意子包名
指的是子包 package.json 中 name 字段的值,不是子包的目录名。
我们可以通过如下命令查看子包间的依赖关系,防止日后理不清子包的依赖关系:
yarn workspaces info
3.2.2.运行script
如果需要执行某个子包中的脚本,可以执行:
yarn workspace <子包名> run build
这样就会运行子包的 build 命令。
3.3.发布
整个开发过程都是由 Yarn 进行管理,但 Yarn Workspace 不具备发布的能力,这时就需要借助 Lerna 的能力了。
3.3.1.检查更新
需要检查自上次发布以来在 Git 中已经提交但尚未发布的修改,执行:
npx lerna changed
该命令的结果形如:
# 没有未发布的修改
lerna notice cli v7.4.2
lerna info versioning independent
lerna notice Current HEAD is already released, skipping change detection.
lerna info No changed packages found# 有未发布的修改
lerna notice cli v7.4.2
lerna info versioning independent
lerna info Looking for changed packages since package1@0.0.1
package1
package2
该命令的结果不仅包含发生修改的子包本身,还包括依赖了该子包的其他子包。比如 package2 发生修改,则依赖它的 package1 也会被识别为需要发布,因为需要修改 package1 依赖的 package2 的版本。
3.3.2.更新版本号
之前说过,在 lerna.json 中配置 version 为 independent 表示每个子包可以独立地更新版本号。假设我们修改了 package1 的代码,首先需要提交修改,否则发布时会因为没有提交记录而被 Lerna 卡住。
接下来需要更新版本号,这一步不需要直接修改 package.json,而是由 Lerna 帮我们修改,执行:
npx lerna version --no-private
–no-private 表示不更新私有包,因为他们不会被发布。命令执行之后,Lerna 会自动识别需要更新版本的子包(即 lerna changed 的结果),并向用户确认下一个版本号是多少。用户依次确认所有需要更新的子包的新版本号后,Lerna 会打 Tag,并push。
这一步不仅会修改 package.json 中的 version,也会修改依赖的版本。比如 package2 更新了,则在 package1 的 package.json 中,不仅自身的版本号会更新,其依赖的 package1 的版本也会跟着改变。
3.3.3.发布
发布就很简单了,执行:
npx lerna publish from-package
即可将更新的子包发布到NPM。
4.踩坑
我是将已经独立发版一段时间的好几个子包改造为使用 Monorepo 架构进行管理。由于 Lerna 基于 Tag 判断有哪些子包需要发布,由于之前发布子包时没有打Tag,所以执行 npx lerna changed
会提示 lerna success found 9 packages ready to publish
,意思是所有的子包都要发布。但执行 npx lerna publish from-package
时由于本地包和 npm 上的版本相同,会提示 No unpublished release found
,意思是没有需要发布的包。
为了解决上述问题,需要补一下Tag,具体方法是执行 npx lerna version --no-private
,Lerna 会让填写每个子包的新版本号,没有修改的子包版本号可以保持不变。执行完毕后再执行 npx lerna changed
会提示 Current HEAD is already released, skipping change detection... No changed packages found
,即达成目的。
随后执行 npx lerna publish from-package
,发布需要更新的子包即可。如果没有要发布的子包,这一步也可以不执行。
5.参考
- Yarn文档
- Yarn Workspaces
- Lerna文档
- Lerna文档翻译
- Lerna 备忘清单
- Lerna + Yarn workspace 使用总结
相关文章:

使用Lerna + Yarn Workspace管理Monorepo项目
1.前言 通常,我们会根据自身业务的实际情况,将通用的组件、逻辑等提取成NPM包,方便以后复用。但这些提取出来的NPM包可能互相之间存在依赖,如果仍然采用 Multirepo 的形式进行管理,则在包的版本管理、依赖管理、调试等…...

如何将gzip后缀压缩包重命名任意后缀名并依然通过gzip.open()读取压缩包文件内容
在 Python 中,gzip.open() 用于解压缩 .gz 后缀的文件。因此,如果您将文件的后缀从 .gz 更改为其他后缀,例如 .diy,然后尝试使用 gzip.open() 打开它,会导致失败,因为 Python 会尝试使用 gzip 解压缩它&…...

C语言从入门到精通 第十一章(文件操作)
写在前面: 本系列专栏主要介绍C语言的相关知识,思路以下面的参考链接教程为主,大部分笔记也出自该教程。除了参考下面的链接教程以外,笔者还参考了其它的一些C语言教材,笔者认为重要的部分大多都会用粗体标注…...

安装安卓studio无法下载sdk解决方法
安装安卓studio无法下载sdk的解决方法如下: 因为google被墙了,android sdk无法下载。 只要修改host文件,就可以下载sdk了。host文件的位置在:C:\Windows\System32\drivers\etc\hosts host文件添加如下内容: #google_…...

express+mysql+vue,从零搭建一个商城管理系统10--添加商品
提示:学习express,搭建管理系统 文章目录 前言一、新建models/goods.js二、新建routes/goods.js三、添加goods表四、添加商品总结 前言 需求:主要学习express,所以先写service部分 一、新建models/goods.js models/goods.js con…...

java实现大文件的分割与合并
最近遇到一个问题,某网盘上传文件时,文件大小超过了4个G ,不能上传,所以就想到了利用的java的IO流,将文件分割成多个小文件,上传到网盘上,等到需要用的时候,下载下来然后再进行文件的…...

【计网】TCP协议安全与风险:深入探讨网络通信的基石
🍎个人博客:个人主页 🏆个人专栏:Linux ⛳️ 功不唐捐,玉汝于成 目录 🌐前言 🔒正文 TCP (Transmission Control Protocol): UDP (User Datagram Protocol): HTTP (Hypertext Transfer …...

苹果App Store上架工具介绍
文章目录 摘要引言正文1. Xcode2. [appuploder](https://www.applicationloader.net/)3. [克魔助手](https://keymob.com/) 4.[ipa guard](https://www.ipaguard.com/)总结参考资料 摘要 苹果App Store作为iOS应用程序的主要分发渠道,上架应用程序需要遵守规定和通…...

TCP重传机制、滑动窗口、拥塞控制
一、总述 TCP,Transmission Control Protocol,是一个面向连接、基于流式传输的可靠传输协议,考虑到的内容很多,比如数据包的丢失、损坏、分片和乱序等,TCP协议通过多种不同的机制来实现可靠传输。今天,重点…...

electron+vue3全家桶+vite项目搭建【29】封装窗口工具类【3】控制窗口定向移动
文章目录 引入实现效果思路声明通用的定位对象主进程模块渲染进程测试效果 引入 demo项目地址 窗口工具类系列文章: 封装窗口工具类【1】雏形 封装窗口工具类【2】窗口组,维护窗口关系 封装窗口工具类【3】控制窗口定向移动 很多时候,我们想…...

深入了解304缓存原理:提升网站性能与加载速度
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...

python-批量操作excel
批量新增excel文件 import osimport xlwings as xwapp xw.App(visibleTrue,add_bookFalse)#visible设置为ture的时候会自动打开创建的excel文件,设为为false的时候不会看到excel文件打开了,实际进程占用了....dept_list [人事部,财务部,研发部,行政部…...

#QT(串口助手-界面)
1.IDE:QTCreator 2.实验:编写串口助手 3.记录 接收框:Plain Text Edit 属性选择:Combo Box 发送框:Line Edit 广告:Group Box (1)仿照现有串口助手设计UI界面 (2)此时串口助手大…...

C语言进阶——位段
在C语言中,位段(Bit Fields)是一种用来对结构体中的成员进行位级别的控制的特性。通过位段,我们可以灵活地控制结构体中各个成员的位数,从而节省内存空间并提高程序的效率。本篇博客将详细讲解C语言中位段的相关知识&a…...

软件设计师软考题目解析23 --每日五题
想说的话:要准备软考了。0.0,其实我是不想考的,但是吧,由于本人已经学完所有知识了,只是被学校的课程给锁在那里了,不然早找工作去了。寻思着反正也无聊,就考个证玩玩。 本人github地址…...

总结:前后端集合、数组类型数据交互底层原理,SpringBoot框架解析
总结:前后端集合、数组类型数据交互底层原理,SpringBoot框架解析 一前后端信息交互本质:1.两台电脑可以通过收发电磁波、控制网线电路开关等基础物理设施,就可以进行物理层面的电信号交互,电信号又可以通过各种传感设备…...

2024蓝桥杯每日一题(前缀和)
一、第一题:壁画 解题思路:前缀和贪心枚举 仔细思考可以发现B值最大的情况是一段连续的长度为n/2上取整的序列的累加和 【Python程序代码】 import math T int(input()) for _ in range(1,1T):n int(input())s input()l math.ceil(len(s)/…...

2007-2022年上市公司迪博内部控制评价缺陷数量数据
2007-2022年上市公司迪博内部控制评价缺陷数量数据 1、时间:2007-2022年 2、范围:上市公司 3、指标:证券代码、证券简称、辖区、证监会行业、申万行业、是否存在财报内控重大缺陷、财报内控重大缺陷数量、是否存在财报内控重要缺陷、财报内…...

JAVA虚拟机实战篇之内存调优[4](内存溢出问题案例)
文章目录 版权声明修复问题内存溢出问题分类 分页查询文章接口的内存溢出问题背景解决思路问题根源解决思路 Mybatis导致的内存溢出问题背景问题根源解决思路 导出大文件内存溢出问题背景问题根源解决思路 ThreadLocal占用大量内存问题背景问题根源解决思路 文章内容审核接口的…...

qt自定义时间选择控件窗口
效果如图: 布局如图: 参考代码: //DateTimeSelectWidget #ifndef DATETIMESELECTWIDGET_H #define DATETIMESELECTWIDGET_H#include <QWidget> #include <QDateTime>namespace Ui { class DateTimeSelectWidget; }class DateTim…...

如何不解压直接读取gzip文件里面的文件
要在服务器上不解压缩的情况下读取gzip文件中的文件内容,您可以使用类似于zlib模块的库,这些库允许您在内存中对gzip数据进行操作而无需解压缩到磁盘上的文件。 在Python中,您可以使用gzip模块来实现这一目的。以下是一个示例代码࿰…...

python 截取字符串string.split
目录 作用语法只要第一个值获得第3个值遍历 作用 根据某个符号对数据进行截取 从而获得自己想要的内容 语法 使用’string.split’ 方法 对字符串’123/abc/BPYC’ 以 ‘/’ 进行截取 string "123/abc/BPYC" substring string.split("/") print(subs…...

SpringBoot+Vue实现el-table表头筛选排序(附源码)
👨💻作者简介:在笑大学牲 🎟️个人主页:无所谓^_^ ps:点赞是免费的,却可以让写博客的作者开心好几天😎 前言 后台系统对table组件的需求是最常见的,不过element-ui的el…...

Linux学习之线程
目录 线程概念 1.什么是线程? 2.线程的优缺点 3.线程异常 4.线程用途 线程操作 1.如何给线程传参 2.线程终止 3.获取返回值 4.分离状态 5.退出线程 线程的用户级地址空间: 线程的局部存储 线程的同步与互斥 互斥量mutex 数据不一致的主要过…...

【JavaEE初阶】 JVM类加载简介
文章目录 🍃前言🌲类加载过程🚩加载🚩验证🚩准备🚩解析🚩初始化 🎄双亲委派模型🚩什么是双亲委派模型?🚩双亲委派模型的优点 ⭕总结 🍃…...

.NET Core依赖注入(IoC)不使用构造函数实现注入
在.NET Core中,依赖注入(IoC)通常是通过构造函数注入来实现的,这是推荐的方式,因为它使得依赖关系更加明确和可测试。但是,如果你不想或不能使用构造函数注入,你可以考虑使用方法注入࿰…...

WinSCP下载安装并结合内网穿透实现固定公网TCP地址访问本地服务器
文章目录 1. 简介2. 软件下载安装:3. SSH链接服务器4. WinSCP使用公网TCP地址链接本地服务器5. WinSCP使用固定公网TCP地址访问服务器 1. 简介 Winscp是一个支持SSH(Secure SHell)的可视化SCP(Secure Copy)文件传输软件,它的主要功能是在本地与远程计…...

内联函数|auto关键字|范围for的语法|指针空值
文章目录 一、内联函数1.1概念1.2特性 二、auto关键字2.2类型别名思考2.3auto简介2.4auto使用细则2.4 auto不能推导的场景 三、基于范围的for循环(C11)3.1 范围for的语法 四、指针空值nullptr(C11)4.1 C98中的指针空值 所属专栏:C初阶 一、内联函数 1.1概念 以inline修饰的函…...

家用洗地机哪个型号好用?介绍几个值得考虑的品牌
作为家里的主要清洁工,我一直以来都是负责家里的清洁工作。我经常使用吸尘器和扫地机器人来轮流清洁,虽然效果还不错,但是这种方式太费时间和精力了。特别是在脸上厨房里做完饭和孩子吃完饭后留下的残渣时,我总是需要用传统的拖多…...

力扣-数组题
1. 两数之和 找出map中是否有target-nums[i], class Solution { public:vector<int> twoSum(vector<int>& nums, int target) {unordered_map<int, int> hash;for(int i 0 ;i < nums.size(); i){if(hash.find(target - nums[i]) ! hash…...