pnpm + monorepo + changesets包管理

使用 pnpm 自带的 monorepo 功能实现单仓多包的代码管理方式,并使用 changesets 更加方便的控制内部包版本管理。

pnpm

pnpm 本质上就是一个包管理器,这一点跟 npm/yarn 没有区别,但它作为杀手锏的两个优势在于:

  • 包安装速度极快
  • 磁盘空间利用非常高效
  • 原生支持monorepo
pnpm-workspace.yaml

存放在项目根目录的一个workspace配置文件,记录着你的子包存放在那些目录下
image.png

包安装速度极快

以一个例子仓库的安装速度举例(https://github.com/pnpm/benchmarks-of-javascript-package-managers):
安装时间对比

actioncachelockfilenode_modulesnpmpnpmYarnYarn PnP
install51s14.4s39.1s29.1s
install5.4s1.3s707msn/a
install10.9s3.9s11s1.8s
install33.4s6.5s26.5s17.2s
install28.3s11.8s23.3s14.2s
install4.6s1.7s22.1sn/a
install6.5s1.3s713msn/a
install6.1s5.4s41.1sn/a
updaten/an/an/a5.1s10.7s35.4s28.3s

在这里插入图片描述

高效利用磁盘空间

当使用 npm 或 Yarn 时,如果你有 100 个项目,并且所有项目都有一个相同的依赖包,那么, 你在硬盘上就需要保存 100 份该相同依赖包的副本。然而,如果是使用 pnpm,依赖包将被 存放在一个统一的位置,因此:

  1. 如果你对同一依赖包需要使用不同的版本,则仅有 版本之间不同的文件会被存储起来。例如,如果某个依赖包包含 100 个文件,其发布了一个新 版本,并且新版本中只有一个文件有修改,则 pnpm update 只需要添加一个 新文件到存储中,而不会因为一个文件的修改而保存依赖包的 所有文件。
  2. 所有文件都保存在硬盘上的统一的位置。当安装软件包时, 其包含的所有文件都会硬链接自此位置,而不会占用 额外的硬盘空间,节省了大量的硬盘空间。这让你可以在项目之间方便地共享相同版本的 依赖包。
  3. 创建非扁平的 node_modules 目录

node-modules-structure-8ab301ddaed3b7530858b233f5b3be57.jpg
当使用 npm 或 Yarn Classic 安装依赖包时,所有软件包都将被提升到 node_modules 的 根目录下。其结果是,pnpm 创建的 node_modules 默认并非扁平结构,因此代码无法对任意软件包进行访问。
默认情况下,pnpm 则是通过使用符号链接的方式仅将项目的直接依赖项添加到 node_modules 的根目录下。 如果你想了解有关 pnpm 所创建的这一独特的 node_modules 结构以 及此结构为何能够在 Node.js 生态系统中很好地工作的话,请阅读以下内容:

npm i react pnpm i react为例
npm i react,可以看到根node_modules下不仅仅只有react包,还有react依赖的包
image.png
pnpm i react,可以看到根目录只有react包和一个.pnpm文件夹
image.png
image.png

monorepo

multirepo困境

在通常情况下,我们新开一个项目会先在 Github 上面创建一个新仓库,然后在本地创建这个项目,和远程仓库进行关联,基本上是一个仓库对应一个项目。这就是 multirepo 模式。

  • 复用代码和配置困难

一旦项目多起来,就会遇到一些更复杂的情况。比如一些独立的 h5 活动页面,这些页面往往是不相关的,不方便部署到一起,需要独立部署到不同域名。除此之外,这些页面可能会有很多共同之处,比如同样的错误处理、同样的多语言文案、同样的 eslint 和 prettier 处理等等。如果有脚手架倒也还好,直接创建一个新项目就行了。但很多团队也没有维护脚手架,每次新开一个项目就是把原来项目的配置给复制粘贴过去,做一些修改,这样效率非常低下

  • 浪费资源,不易管理

每次有一个新的页面就去创建一个项目,这些项目也会过于分散,不便管理。还会白白浪费资源,比如它们可能都会安装 React、React-dom 等包,导致每个项目 node_module 重复过大。

  • 调试麻烦

如果你想在本地项目进行调试,但这个项目依赖了另一个项目,那么你只能用 npm link 的方式将它 link 到需要调试的项目里面。一旦 link 的项目多了,手动去管理这些 link 操作就容易心累,进一步就会发展到摔键盘、砸显示器。

monorepo

monorepo 就是把多个项目的所有代码放到一个 git 仓库中进行管理,多个项目中会有共享的代码则可以分包引用。整个项目就是有 root 管理的 dependencies 加上多个 packages,每个 package 也可以在自己的作用域引入自己的 dependencies。市面上流行的库react、vue、bable等都采用 monorepo 方式进行管理。
image.png
早前的monorepo使用大多数是基于yarn + lerna但是这样配置复杂,新手难以理解,而 lerna 的很多功能其实相对于来说是很冗余的。Vue3 的仓库管理形式就从yarn + lerna 的 monorepo 形式整体迁移成了 pnpm 进行管理。

pnpm + monorepo简单使用

全局依赖

pnpm install lodash -D -W

  • -D 把依赖作为devDependencies安装,-W 把依赖安装到根目录的 node_modules 。
  • 虽然 packages 下的项目(子包)都没有安装 lodash ,但是倘若在项目中使用到,就会逐级往上寻找。
局部依赖

pnpm install jquery -r --filter @monorepo/package-a

  • 对于某些依赖,可能仅存在于某几个 package 中,我们就可以单独为他们安装,当然,可以通过** cd packges/xxx**后,执行pnpm install xxx 但这样重复操作多次未免有些麻烦,pnpm 提供了一个快捷指 –filter
  • 比如我们只在 package-a 应用中用到 JQuery,那就可以为它单独安装。首先要拿到它的 package name,在本例中是 @monorepo/package-a:
  • 在packages/package-a/package.json中,我们可以看到:
"dependencies": {
  "jquery": "^3.6.0"
 }
内部包的相互引用

pnpm i @monorepo/package-a -r --filter @monorepo/package-b

  • monorepo 中,我们往往需要 package 间的引用,比如本例中的 @monorepo/package-a 就会被@monorepo/package-b引用。
  • 在 packages/package-b/package.json 中,我们可以看到:
"dependencies": {
  "@monorepo/package-a": "workspace:^1.0.0"
}
  • 这时你会有一个疑问:当这样的工具包被发布到平台后,如何识别其中的 workspace 呢?
  • 那就需要执行 pnpm publish,会把基于的workspace的依赖变成外部依赖
// before
"dependencies": {
  "@monorepo/package-a": "workspace:^1.0.0"
}
// after
"dependencies": {
  "@monorepo/package-a": "^1.0.0"
}
依赖安装

pnpm install

  • pnpm install 会同时为根目录和所有 package 安装需要的依赖

Changesets

changesets 主要关心 monorepo 项目下子项目版本的更新、changelog 文件生成、包的发布。一个 changeset 是个包含了在某个分支或者 commit 上改动信息的 md 文件,它会包含这样一些信息:

  • 需要发布的包
  • 包版本的更新层级(遵循 semver 规范)
  • CHANGELOG 信息

在 changesets 工作流会将开发者分为两类人,一类是项目的维护者,还有一类为项目的开发者,两者的职责可以通过如下流程图很简洁的表示出来:
v2-41ad35179f4085715f244302b148dd5b_1440w.jpg
根据上图, changesets 的工作流程是这样:开发者在 monorepo 项目下进行开发,开发完成后,给对应的子项目添加一个 changesets 文件。项目的维护者后面会通过 changesets 来消耗掉这些文件并自动修改掉对应包的版本以及生成 CHANGELOG 文件,最后将对应的包发布出去。

安装

在项目根目录执行 pnpm install @changesets/cli 即可

初始化

终端执行 npx changeset init 。在项目根目录下生成一个 .changeset 目录,里面会生成一个 changeset 的 config 文件。一般不需要去做修改。
image.png
注: 如果 config 中有个 access,如果后面 npm 发版时,npm报错 unscoped 之类的,可以把这里修改为public。默认是另外一个值,可以在官方github中查看。

add

npx changeset add
add 在 changesets 中算得上比较关键的命令之一了,它会根据 monorepo 下的项目来生成一个 changeset 文件,里面会包含前面提到的 changeset 文件信息(更新包名称、版本层级、CHANGELOG 信息)。
执行知乎让我们选择本次 changeset 需要发布的包,这些包名都是 monorepo 项目下的子包。
然后让我们选择需要追加发布的版本。按回车表示跳过。major(主版本号)| mino(次版本号)| patch(修订号)。都不选的话最后会自动选择 patch也就是比如从 1.0.0的版本更改为1.0.1)。然后输入一个描述信息,一路回车就可以了。注意:如果是发布 beta 版本的话,这些都可以不选,后面我们会说到发布测试版本的不同。
在填完 summay 总结之后,后面会生成一个文件名称随机的 changeset 文件。
在这里插入图片描述

image.png
这里文件的名称是通过一个叫做 human-id 的库生成的,具体可以在 npm 上查看,但实际上这里用户也是可以自行修改文件名称的,这里并没有太大的关系,也可以修改文件里面的 CHANGELOG 的信息。
这个文件本质上是做个信息的预存储,在该文件被消耗之前,用于是可以自定义修改的。随着不同开发者的迭代积累,changeset 文件是可以在一个周期之内进行累积的。
如果有信息相同,只是 CHANGELOG 描述不同的 changeset 文件,在消耗这些文件的时候是会被合并处理的,即对应包的 version 并不会被升级多次

version

npx changeset version
这个命令这里可以当作 bump version 来理解,这里本质上做的工作是消耗 changeset 文件并且修改对应包版本以及依赖该包的内部包版本,同时会根据之前 changeset 文件里面的信息来生成对应的 CHANGELOG 信息。会根据相应的信息修改掉包版本、消耗掉 changeset 文件、同时更新掉 CHANGELOG 文件(如果没有就新生成一个)。
例如现在在 pnpm 仓库的根目录下执行一次 npx changeset version。那么就会更新之前 npx changeset add 所选择的包及其 package.json 中对应的版本号和消耗掉对应的文件。

publish

npx changeset publish
本质上就是对npm publish做了一次封装,同时会检查对应的npm registry上有没有对应包的版本,如果已经存在了,就不会再发包了,如果不存在会对对应的包版本执行一次 pnpm publish
在这里插入图片描述

其他

中文官网:https://pnpm.io/zh/

  • 28
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值