前言
在进行pnpm+monorepo项目的开发的时候,我们总是会碰到这样一些问题,依赖重叠,同一包但是由于各个子项目需求不同导致了同一包产生了不同版本的问题等等,这种问题的出现不仅会导致项目构建速度慢,项目构建体积大,而且还非常不利于我们后期对项目依赖进行管理,于是变量提升这个方法就孕育而生了。
Why hoist?
在monorepo项目中,hoist的作用是将子项目中的node_moudles中的某些依赖提升到根目录中的node_modules中进行管理,从而避免某些依赖的重复安装,提高构建速度,减少磁盘空间。
pnpm的hoisting机制
默认的histing行为
通常情况下,hositing默认如下
依赖共享:
当多个子项目同一个依赖时,pnpm会将其提升至根目录的node_moudles中,避免重复安装,这种方式被称为hoisting
例如:
根项目pka
{
"dependencies": {
"lodash": "^4.17.21"
}
}
子项目sdk-a的pka
{
"dependencies": {
"lodash": "^4.17.21"
}
}
这样的结构pnpm会默认讲loadsh提升至更目录的node_moudls中
hoisting层次结构:
pnpm 会根据依赖的类型和作用域来决定是否以及如何提升它们。通常情况下,dependencies 会被提升到根目录,而 devDependencies 和 peerDependencies 会根据需求提升。
避免冲突:
hoist会避免同一包不同版本的依赖被hoist,如果存在同一包不同版本的依赖,那么hoist会把依赖展现在各自的子项目中的node_moudles中,而不是把它们全部提升到根目录中。
pnpm中的hoist配置
pnpm的hoist配置选项
hoist提供了以下两种方式进行hoist配置
Hoistpattern
-
控制哪些依赖会被提升到根项目目录中
-
默认情况下,hoist会自动提升子项目中的dependcies中的依赖到根目录中,但是我们可以使用hoistpattern进行指定的依赖进行提升,也就是说,如果你想让react这个依赖被提升至根目录中,可以在pnpm-workspace.ymal中进行配置
.ymal
hoistPattern:
- 'react'
- 'react-dom'
这样的话,react和react-dom就会被提升到根项目目录中,而其他依赖则会继续保存在子项目中的node_moudles中。
Shamefullyhoist:
默认情况下,hoist会避免将一些常见的依赖在根项目的node_moudles中,但是如果我们设置了shamefullyhoist后,hoist会将所有的依赖提升至根目录中
.ymal
shamefullyHoist: true
设置shamefullyHoist为true后,hoist就会将所有的依赖提升至根目录中(包括devDependencies和poorDependecies)但是这个会有一个问题,如果将所有的依赖都提升至根目录中,这样会使根目录的node_moudles变得无比庞大,从而不好进行管理。
ps: 为什么hoist会避免将一些常见的依赖提升至根项目的node_moudles呢?
避免版本冲突,控制依赖的提升。
pnpm默认的hoisting行为
pnpm默认的hoisting如下:
-
dependencies:如果子项目中申明了依赖,那么这个依赖就会提升至根目录中的node_noudles,
除非在根目录中被申明为dependencies或者poordependencies。
-
devDependencies: 默认情况下,devDependencies 不会被 hoisted 到根目录,而是保留在子项目的 node_modules 中。
-
peerDependencies:peerDependencies 是一个特殊的依赖类型,pnpm 会将它们提升到根目录,并期望用户在根项目中安装它们。即使你没有显式安装,pnpm 也会尝试提升和安装所有需要的 peerDependencies。
hoist存在的一些问题
版本冲突
hoist会避免将一些同包不同版本的依赖提升至根目录,但是在一些情况下会导致版本冲突问题,那么有没有什么方式进行处理呢,当然是有的,我们后面提。
意外提升问题
有时候hoist会不小心提升了一些不必要的依赖,导致根目录的node_moudles变得非常臃肿 ,从而导致不好维护。
More
我们上面提了如果出现了同一包不同版本的问题,我们能不能从根源上解决这个问题呢?
答案是有的,我们引进一下resolutions配置
What is resolutions?
我们引进一下gpt对他的定义
在现代 pnpm(尤其是使用 pka 工作区)中,resolution 通常是用来指定如何解析依赖的版本。它允许开发者控制依赖树中具体模块的版本解析行为,类似于 pnpm.overrides 的功能,但更偏向于解决工作区和内部模块的特定需求。
resolutions在pka中的作用
resolutions的作用就是强制指定依赖的版本,无论是顶层依赖还是子依赖,都会遵循这一个版本
resolutios的配置
强制顶层依赖的配置
{
"resolutions": {
"react": "^18.0.0",
"typescript": "5.2.2"
}
}
-
react@18.0.0:整个项目的react保持在18.0.0以上的版本
-
"typescript": "5.2.2":整个项目typescript保持在5.2.2以上的版本
-
锁定子依赖的版本
{ "resolutions": { "**/lodash": "4.17.21" } }
-
"**/lodash":强制依赖树下的loadsh均为4.17.21的版本
-
"axios@^0.27.0":指定0.27.0的版本转化为0.27.2的版本
-
解决版本冲突
如果一个包有多个版本可以配置解决管理冲突
{
"resolutions": {
"axios@^0.27.0": "0.27.2"
}
}
-
"axios@^0.27.0":指定0.27.0的版本转化为0.27.2的版本
工作原理
-
resolutions的工作规则会应用于整个依赖树当中,不管是直接依赖还是间接依赖
-
当出现版本冲突问题时,resolutions会覆盖依赖管理工具的默认行为,也就是说resolution的优先级更高
-
resolutions的工作规则在install后会被保存在lock文件中,即不会因为你对pka文件的修改而改变或者导致问题,保持一致性
What is overrides?
在pnpm中,虽然pnpm不直接支持resolutions字段的使用,但是它提供了overrides这种类似于resolutions功能的字段
pnpm.overriders是一种强制性转换版本的替换机制,具有以下功能:
-
解决依赖版本问题:强制整个依赖树使用统一的依赖版本
-
快速修复问题:如果某个依赖存在问题(如 Bug),但上游包尚未更新,你可以临时覆盖该依赖的版本。
-
锁定版本:固定版本,减少不确定性
配置overriders
下面是一个典型的overriders配置
{
"pnpm": {
"overrides": {
"lodash": "^4.17.21",
"react-dom@18.0.0": "18.0.2",
"typescript": "5.2.2"
}
}
}
-
"lodash": "^4.17.21":强制指定依赖树版本都是4.17.21
-
"react-dom@18.0.0": "18.0.2":将18.0.0版本转换为18.0.2
-
"typescript": "5.2.2": 锁定所有版本都是5.2.2
使用通配符进行配置
{
"pnpm": {
"overrides": {
"axios@<1.5.0": "1.5.0"
}
}
}
-
这个配置说明pnpm会将axios中小于1.5.0的版本全部替换为1.5.0的版本
修复特定的包的子版本
{
"pnpm": {
"overrides": {
"foo>bar": "1.1.0"
}
}
}
-
这个配置说明如果foo包内的子依赖bar出现问题,并且版本是@1.0.0,那么他会修复成1.1.0的版本
总结:
pnpm.overrides 是 pnpm 中功能强大的工具,可以有效解决依赖冲突和版本控制问题。与 yarn resolutions 相比,它更灵活,支持范围指定、通配符覆盖和更强的依赖树控制能力,是 pnpm 管理依赖的一大亮点