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. 造成一定程度的幻影依赖,但瑕不掩瑜