Pnpm 是社区近几年最受关注的的包管理工具。它用符号链接组织 node_modules 结构;用硬链接共享依赖包。如果你不明白这两点代表什么,建议先阅读官方文档和实践后回过头来看这篇文章
理解 hardlink 和 symlink
inode
: 存储文件元信息,Unix/Linux
系统内部使用inode
号码来识别文件
hardlink
: 是一个链接文件,和源文件指向同一个inode
;因此删除其中一个文件不影响另一个文件的访问,文件内容的修改会同步到所有文件
symlink
: 软链接(AKA
符号链接),是一个链接文件,指向源文件的地址;因此修改源文件内容,软链接内容也会改变。当删除源文件时,访问软链接会报错No such file or directory
Node 依赖查找规则
hardlink
: 从硬链接文件位置开始查找以来,与源文件位置无关
symlink
: 从源文件位置开始查找依赖。用-preserve-symlinks
可修改查询规则为从软链接文件位置
Pnpm 的 package-import-method
auto
: 默认值,尝试clone
包,如果不支持则按hardlink
/copy
降级
clone
: 从store
中克隆packages
。(AKA
写时复制或引用链接)
hardlink
: 从store
中硬链接packages
。缺点是修改node_modules
中的packages
会影响store
copy
: 从store
中复制packages
clone-or-copy
: 尝试clone
,如果不支持降级为copy
最理想且性能最好的情况是
clone
,但目前写时复制在各个操作系统的兼容性方面仍是一个问题。Pnpm 的 hosit 相关属性
hoist
: 默认true
,依赖项提升到node_modules/.pnpm
;未列出的依赖项将在.pnpm/node_modules
中
semi-strict
模式,应用仅能访问到依赖项,但依赖之间能够互相访问;node_modules/.modules.yaml
的 hoistedDependencies
字段可以看到所有被提升的依赖。hoist-pattern
: 指示pnpm
将匹配模式的依赖提升到node_modules/.pnpm
;即放入.pnpm/node_modules
目录下
public-hoist-pattern
: 默认为['*eslint*', '*prettier*']
,指示pnpm
将匹配模式的依赖提升到根模块目录
shamefully-hoist
: 默认为false
,为true
时相当于public-hoist-pattern
为*
Pnpm 的 node_modules 配置
shamefully-hoist=true
: 最松散的模式。类似npm
将所有依赖提升到根目录;应用和依赖,依赖和依赖都存在幻影依赖
hoist=true
:semi-strict
模式。应用仅能访问到依赖项,依赖间仍能互相访问;但范围仅限于当前项目,如果在当前项目外仍有node_modules
应用代码仍能访问幻影依赖(例如monorepo
架构时根目录存在node_modules
)
host=false
:strict
模式。应用和依赖都无法访问幻影依赖,但范围同样仅限于当前项目。
但因为社区中存在幻影依赖的
Library
非常多,因此 Pnpm
的默认策略是 semi-strict
模式。Pnpm 的 node_modules 设计
资料:
要点:
hardlink
指向依赖项,symlink
构建嵌套的依赖关系图结构
- 合理的结构避免循环
symlink
- 兼容现有的
Node
和npm
特性:允许包导入自己
- 包与它们的依赖关系很好地分组
Pnpm 如何控制包的版本
pnpm.overrides
/resolutions
: 覆盖依赖关系图中的任何依赖关系,强制所有包使用依赖项的单一版本
pnpm.packageExtensions
: 扩展现有包定义的方法
.pnpmfile.cjs
: 通过.pnpmfile.cjs
中的hook
直接侵入到pnpm
的包安装过程
Pnpm 中 peerDependencies 不同时的包分身问题
项目中对于同一个包的同一个版本,
pnpm
最终都指向同一个 hardlink
文件。但如果:- 项目包含
2
次依赖foo@1.0.0
2
次依赖的foo
虽然版本一致,但其peerDependencies
存在差异
Pnpm
则会创建多个依赖集,此时会有两个 foo
的 hardlink
,构建工具打包时如果没有特殊处理就会打包进多个实例。Ps. 目前该行为是符合预期的,
npm
/ yarn
/ pnpm
都是该行为解决方案:
- 用
Pnpm
控制包的安装版本或过程
- 开启
dedupe-peer-dependents
- 构建工具用
alias
指定包为同一个实例
Pnpm 的 .npmrc 配置最佳实践
Peer Dependency Settings
:dedupe-peer-dependents
: 指示Pnpm
项目依赖中的相同版本的peer deps
指向同一个实例
resolve-peers-from-workspace-root
:- 在
monorepo
下子项目需要在声明peer deps
的同时在dev deps
再写一遍,否则子项目开发时就会缺乏该依赖 - 假如此时
B
项目依赖该A
项目,B
项目中就需要安装A
的peer deps
,整个workspace
中就存在两份A
的peer deps
- 如果
A
的peer deps
版本改了,B
的peer deps
并不随之改变,除非重新pnpm install
- 重复写
peer deps
和dev deps
比较麻烦
这会造成
2
个小问题:解决方案:开启
resolve-peers-from-workspace-root
指示 Pnpm
将 workspace
中所有项目的 peer deps
安装到根目录 .pnpm
目录下,确保所有项目共享 peer deps
,子包依赖能互相访问且版本一致。Ps. 造成一定程度的幻影依赖,但瑕不掩瑜