lerna 还是 pnpm + changesets

2023-04-25
768426 分钟
...

monorepo 是多个包在同一个项目中管理的方式,是很流行的项目组织形式。

主流的开源包基本都是用 monorepo 的形式管理的。

为什么用 monorepo 也很容易理解:

比如 babel 分为了 @babel/core、@babel/cli、@babel/parser、@babel/traverse、@babel/generator 等一系列包。

如果每个包单独一个仓库,那就有十多个 git 仓库,这些 git 仓库每个都要单独来一套编译、lint、发包等工程化的工具和配置,重复十多次。

工程化部分重复还不是最大的问题,最大的问题还是这三个:

  1. 一个项目依赖了一个本地还在开发的包,我们会通过 npm link 的方式把这个包 link 到全局,然后再 link 到那个项目的 node_modules 下。

npm link 的文档是这么写的:

就是把代码 link 到全局再 link 到另一个项目,这样只要这个包的代码改了,那个项目就可以直接使用最新的代码。

如果只是一个包的话,npm link 还是方便的。但现在有十几个包了,这样来十多次就很麻烦了。

2. 需要在每个包里执行命令,现在也是要分别进入到不同的目录下来执行十多次。最关键的是有一些包需要根据依赖关系来确定执行命令的先后顺序。

3. 版本更新的时候,要手动更新所有包的版本,如果这个包更新了,那么依赖它的包也要发个新版本才行。

这也是件麻烦的事情。

因为这三个问题:npm link 比较麻烦、执行命令比较麻烦、版本更新比较麻烦,所以就有了对 monorepo 的项目组织形式和工具的需求。

比如主流的 monorepo 工具 lerna,它描述自己解决的三个大问题也是这个:

也就是说,把理清了这三个点,就算是掌握了 monorepo 工具的关键了。

我们分别来看一下:

npm link 的流程实际上是这样的:

npm 包先 link 到全局,再 link 到另一个项目的 node_modules。

而 monorepo 工具都是这样做的:

比如一个 monorepo 项目下有 a、b、c 三个包,那么 monorepo 工具会把它们 link 到父级目录的 node_modules。

node 查找模块的时候,一层层往上查找,就都能找到彼此了,就完成了 a、b、c 的相互依赖。

比如用 lerna 的 demo 项目试试:

git clone https://github.com/lerna/getting-started-example.git 

下载下来是这样的结构:

执行 npm install,在根目录的 node_modules 下就会安装很多依赖。

包括我们刚说的 link 到根 node_modules 里的包:

这个箭头就是软链接文件的意思。

底层都是系统提供的 ln -s 的命令。

比如我执行

ln -s package.json package2.json 

那就是创建一个 package2.json 的软连接文件,内容和 package.json 一样。

这俩其实是一个文件,一个改了另一个也就改了:

动图封面

原理都是软连接,只不过 npm link 的那个和 monorepo 这个封装的有点区别。

这种功能本来是 lerna 先实现的,它提供了 lerna bootstrap 来完成这种 link:

只不过后来 npm、yarn、pnpm 都内置了这个功能,叫做 workspace。就不再需要 lerna 这个 bootstrap 的命令了。

直接在 package.json 里配置 workspace 的目录:

然后 npm install,就会完成这些 package 的 link。

而包与包之间的依赖,workspace 会处理,本地开发的时候只需要写 * 就好,发布这个包的时候才会替换成具体的版本。

这里用的是 npm workspace:

它所解决的问题正如我们分析的:

在 npm install 的时候自动 link。

yarn workspace 也是一样的方式:

pnpm 有所不同,是放在一个 yaml 文件里的:

此外,yarn 和 pnpm 支持 workspace 协议,需要把依赖改为这样的形式:

这样查找依赖就是从 workspace 里查找,而不是从 npm 仓库了。

总之,不管是 npm workspace、yarn workspace 还是 pnpm workspace,都能达到在 npm install 的时候自动 link 的目的。

回过头来再来看 monorepo 工具的第二大功能:执行命令

在刚才的 demo 项目下执行

lerna run build

输出是这样的:

lerna 会按照依赖的拓扑顺序来执行命令,并且合并输出执行结果。

比如 remixapp 依赖了 header 和 footer 包,所以先在 footer 和 header 下执行,再在 remixapp 下执行。

当然,npm workspace、yarn workspace、pnpm workspace 也是提供了多包执行命令的支持的。

npm workspace 执行刚才的命令是这样的:

npm exec --workspaces -- npm run build 

可以简写为:

npm exec -ws -- npm run build 

也可以单独执行某个包下执行:

npm exec --workspace header --workspace footer -- npm run build

可以简写为:

npm exec -w header -w footer  -- npm run build

只不过不支持拓扑顺序。

yarn workspace 可以执行:

yarn workspaces run build 

但也同样不支持拓扑顺序。

我们再来试试 pnpm workspace。

npm workspace 和 yarn workspace 只要在 package.json 里声明 workspaces 就可以。

但 pnpm workspace 要声明在 pnpm-workspaces.yaml 里:

pnpm 在 workspace 执行命令是这样的:

pnpm exec -r pnpm run build 

-r 是递归的意思:

关键是 pnpm 是支持选择拓扑排序,然后再执行命令的:

有时候命令有执行先后顺序的要求的时候就很有用了。

总之,npm、yarn、pnpm 都和 lerna 一样支持 workspace 下命令的执行,而且 pnpm 和 lerna 都是支持拓扑排序的。

再来看最后一个 monorepo 工具的功能:版本管理和发布。

有个工具叫做 changesets 是专门做这个的,我们看下它能做啥就好了。

执行 changeset init:

npx changeset init

执行之后会多这样一个目录:

然后添加一个 changeset。

什么叫 changeset 呢?

就是一次改动的集合,可能一次改动会涉及到多个 package,多个包的版本更新,这合起来叫做一个 changeset。

我们执行 add 命令添加一个 changeset:

npx changeset add 

会让你选一个项目:

哪个是 major 版本更新,哪个是 minor 版本更新,剩下的就是 pacth 版本更新。

1.2.3 这里面 1 就是 major 版本、2 是 minor 版本、3 是 patch 版本。

之后会让你输入这次变更的信息:

然后你就会发现在 .changeset 下多了一个文件记录着这次变更的信息:

然后你可以执行 version 命令来生成最终的 CHANGELOG.md 还有更新版本信息:

npx changeset version 

之后那些临时的 changeset 文件就消失了:

更改的包下都多了 CHANGELOG.md 文件:

并且都更新了版本号:

而且 remixapp 这个包虽然没有更新,但是因为依赖的包更新了,所以也更新了一个 patch 版本:

这就是 changeset 的作用。

如果没有这个工具呢?

你要自己一个个去更新版本号,而且你还得分析依赖关系,知道这个包被哪些包用到了,再去更改那些依赖这个包的包的版本。

就很麻烦。

这就是 monorepo 工具的版本更新功能。

更新完版本自然是要 publish 到 npm 仓库的。

执行 changeset publish 命令就可以,并且还会自动打 tag:

如果你不想用 changeset publish 来发布,想用 pnpm publish,那也可以用 changeset 来打标签:

npx changeset tag 

这就是 monorepo 工具的版本更新和发布的功能。

lerna 是自己实现的一套,但是用 pnpm workspace + changeset 也完全可以做到。

回过头来看下这三个功能:

不同包的自动 link,npm workspace、yarn workspace、pnpm workspace 都可以做到,而 lerna bootstrap 也废弃了,改成基于 workspace。

执行命令这个也是都可以,只不过 lerna 和 pnpm workspace 都支持拓扑顺序执行命令。

版本更新和发布这个用 changeset 也能实现,用 lerna 的也可以。

整体看下来,似乎没啥必要用 lerna 了,用 pnpm workspace + changesets 就完全能覆盖这些需求。

那用 lerna 的意义在哪呢?

虽然功能上没啥差别,但性能还是有差别的。

lerna 还支持命令执行缓存,再就是可以分布式执行任务。

执行 lerna add-caching 来添加缓存的支持:

指定 build 和 test 命令是可以缓存的,输出目录是 dist。

那当再次执行的时候,如果没有变动,lerna 就会直接输出上次的结果,不会重新执行命令。

下面分别是第一次和第二次执行:

至于分布式执行任务这个,是 nx cloud 的功能,貌似是可以在多台机器上跑任务。

所以综合看下来,lerna 在功能上和 pnpm workspace + changesets 没啥打的区别,但是在性能上更好点。

如果项目比较大,用 lerna 还是不错的,否则用 pnpm workspace + changesets 也完全够用了。

总结

monorepo 是在一个项目中管理多个包的项目组织形式。

它能解决很多问题:工程化配置重复、link 麻烦、执行命令麻烦、版本更新麻烦等。

lerna 在文档中说它解决了 3 个 monorepo 最大的问题:

  • 不同包的自动 link
  • 命令的按顺序执行
  • 版本更新、自动 tag、发布

这三个问题是 monorepo 的核心问题。

第一个问题用 pmpm workspace、npm workspace、yarn workspace 都可以解决。

第二个问题用 pnpm exec 也可以保证按照拓扑顺序执行,或者用 npm exec 或者 yarn exec 也可以。

第三个问题用 changesets 就可以做到。

lerna 在功能上和 pnpm workspace + changesets 并没有大的差别,主要是它做了命令缓存、分布式执行任务等性能的优化。

总之,monorepo 工具的核心就是解决这三个问题。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
pnpm2026-03-17
Bun 为什么比 pnpm(以及 npm、Yarn)快
Bun 之所以比 pnpm(以及 npm、Yarn)快,核心原因在于其 底层架构的彻底重构 。pnpm 虽然通过硬链接和内容寻址解决了磁盘空间和部分安装效率问题,但它本质上仍是一个运行在 Node.js 之上的 JavaScript 工具;而 Bun 是从底层用 Zig 语言 编写的原生二进制程序,并深度集成了 JavaScriptCore 引擎。 以下是 Bun 比 pnpm 快的具体技术原因: 1\. 编程语言与执行环境的差异(最根...
学习
pnpm2025-09-01
📦 npm、yarn、pnpm、bun 是什么
🎯 什么是包管理器? 在 JavaScript 和 Node.js 生态中,一个项目往往依赖很多第三方库,比如 Vue、React、Lodash 等。\ \ 包管理器(Package Manager)\ \ 的作用,就是: 安装/卸载这些依赖库; 管理依赖的版本; 构建依赖树; 提供运行脚本(如 npm run dev)等功能。 最早是 npm,后来有了性能更好的 yarn,又有了极致节省空间的 pnpm,最近还杀出了一个全能选手 —...
学习
pnpm2024-09-29
【npm私有化部署】手把手教你使用Verdaccio搭建私有化npm
一、Verdaccio是什么? 是一个 Node.js创建的轻量的私有npm代理注册源(proxy registry) 通过Verdaccio搭建私有npm服务器有着以下优势: 1. 零配置:无需安装数据库,基于 ,安装及运行。 2. 使用方便:将内部高复用的代码进行提取,方便在多个项目中引用。 3. 安全性:仓库搭建在局域网内部,只针对内部人员使用。 4. 权限管理:对发布和下载npm包配置权限管理。 5. 加速包下载:将下载过的依赖...
学习
pnpm2024-09-11
NPM私库搭建-verdaccio(Linux)
linux服务器安装node a)、官网下载所需的node版本 b)、解压安装包 若下载的是xxx.tar.xz文件,解压命令为 c)、修改环境变量 修改:/etc/profile文件 d)、环境变量需重启生效 e)、查看当前node版本 防止报grywarn权限的错 是因为原来的私库崩了 这里直接指明仓库地址为淘宝镜像 文件位置: 如果找不到该文件,需要先运行verdaccio,直接执行verdaccio命令即可。 继续修改配置文件:...
学习
pnpm2024-09-02
【npm进阶】探索npx:一次性执行包的新方式
在现代前端开发中,开发者们需要管理和使用各种各样的npm包。而在此过程中, 作为npm生态系统中的一部分,提供了一种方便的方式来执行本地或远程的npm包。本篇文章将详细介绍 的用途和优势,展示如何使用 来执行本地和远程包,并提供一些实际的npx示例。 引言 在过去,开发者通常会通过全局安装某些工具来使用它们。然而,全局安装不仅会污染全局环境,还可能导致不同项目之间的版本冲突。 是npm 5.2.0引入的一种新工具,旨在解决这些问题。通过...
学习
pnpm2024-07-10
外网向内网导包的坎坷之旅
最近工作遇到个问题,一开始项目是在外网运行的,后来需要导入内网运行,我一开始是把node modules整个目录拷贝过去了,结果运行不起来,可能因为项目是pnpm的,直接从mac拷贝到windows似乎是运行不起来。然后尝试自己下载,缺了好多包,也启动不了运行不起来,最后只好开始了我的导包之旅。 这是一个悲伤的故事,前前后后和对应的人员来回导包4、5次,历时几周才搞完,因为项目不着急,所以就没有急着去搞,真是尴尬。下面就是我的导包之旅:...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录