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/

<think>好的,我现在需要帮用户找到关于monorepo的详细教程或使用指南。首先,我得回顾用户提供的引用内容,看看里面有没有相关的信息。引用[2]提到了使用pnpm来管理monorepo,推荐它作为2022年后的工具,因为它在处理依赖和提升体验方面表现很好。这里可能是一个重要的点,用户可能需要了解如何用pnpm搭建monorepo环境。 然后引用[3]比较了不同的工具,比如lerna和rush,指出lerna的上手成本高,需要yarn,而rush可能也有其复杂性。用户可能对选择哪个工具比较感兴趣,所以应该提到这些工具的优缺点,以及为什么推荐pnpm。 引用[1]提到package.json中的private设置为true,因为monorepo本身不发布,而是发布子项目。这可能涉及到配置方面的细节,用户需要知道如何正确设置他们的项目结构。 接下来,我需要整理一个结构清晰的教程步骤。首先介绍monorepo的概念,然后选择合适的工具,比如pnpm。接着是初始化项目,配置workspace,处理依赖管理,最后是构建和发布。每个步骤都需要结合引用中的信息,比如使用pnpm的workspace功能,设置package.json的private字段等。 可能用户还会关心如何管理内部依赖,比如子包之间的引用。这时候需要提到在子项目的package.json中使用workspace协议,如"dependencies": { "shared": "workspace:*" },这样pnpm会自动处理内部依赖。 另外,用户可能需要知道如何添加、构建和发布子项目。这里要分步骤说明,比如使用pnpm add安装依赖,通过pnpm -F指定子项目,以及使用变更集工具来管理版本和发布流程。 最后,应该包含常见问题,比如如何处理幽灵依赖,以及为什么选择pnpm而不是其他工具。这些问题可能在用户实际使用中会遇到,提前解答能帮助他们避免踩坑。 总结一下,回答需要涵盖工具选择、项目初始化、依赖管理、构建发布流程,以及常见问题解答,结合用户提供的引用内容,确保信息准确且有参考依据。</think>### Monorepo 教程与使用指南详解 #### 1. **Monorepo 核心概念** Monorepo(单仓库)是一种将多个项目或模块存放在同一个代码仓库中的管理方式。其核心优势包括: - **代码共享便捷**:内部模块可直接引用,避免重复开发。 - **统一版本管理**:依赖和版本变更更易协调。 - **协作效率提升**:跨团队协作时,代码变更可见性高[^2]。 #### 2. **工具选择与配置** 当前主流的 Monorepo 管理工具推荐 **pnpm**,原因如下: - **高效依赖管理**:通过硬链接节省磁盘空间,加速安装速度。 - **原生 Workspace 支持**:无需复杂配置即可定义子模块关系[^2]。 - **避免幽灵依赖**:显式声明依赖,减少隐式依赖问题[^3]。 **初始化步骤**: ```bash # 全局安装 pnpm npm install -g pnpm # 创建项目目录并初始化 mkdir monorepo-demo && cd monorepo-demo pnpm init ``` #### 3. **Workspace 配置** 在根目录创建 `pnpm-workspace.yaml` 文件,定义子模块: ```yaml packages: - "packages/**" # 所有子项目存放在 packages 目录下 - "docs" # 可添加其他目录 ``` **子项目结构示例**: ``` monorepo-demo/ ├── package.json ├── pnpm-workspace.yaml └── packages/ ├── shared/ # 公共工具库 │ └── package.json └── app/ # 主应用 └── package.json ``` #### 4. **依赖管理实践** - **公共依赖**(如 React、TypeScript)安装到根目录: ```bash pnpm add react typescript -w ``` - **子项目专属依赖**(如 app 私有包): ```bash pnpm add lodash -F app ``` - **内部依赖引用**(app 依赖 shared): 在 `app/package.json` 中添加: ```json { "dependencies": { "shared": "workspace:*" } } ``` #### 5. **构建与发布流程** - **构建所有子项目**: ```bash pnpm -r run build ``` - **版本管理与发布**: 使用 `changesets` 工具管理版本变更: ```bash pnpm add @changesets/cli -w pnpm changeset # 生成变更记录 pnpm changeset publish # 发布版本 ``` 该工具支持自动生成 CHANGELOG 和协调子包版本号[^3]。 #### 6. **常见问题与优化** - **幽灵依赖问题**:通过 pnpm 的严格模式(`shamefully-hoist=false`)强制所有依赖显式声明[^3]。 - **跨平台协作**:建议搭配 Git 子模块或 Plastic SCM 实现高效代码同步[^4]。 - **性能优化**:利用 pnpm 的过滤命令仅构建受影响模块: ```bash pnpm --filter=app... run build ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值