Skip to content

pnpm

pnpm 项目初衷

  • 节省磁盘空间:依赖会被存储在内容可寻址的存储中
    • 如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库(变动小)
    • 所有文件都会存储在硬盘上的某一位置(跨项目共享)
  • 提高安装速度
    • 依赖解析。 仓库中没有的依赖都被识别并获取到仓库。
    • 目录结构计算。 node_modules 目录结构是根据依赖计算出来的。
    • 链接依赖项。 所有以前安装过的依赖项都会直接从存储区中获取并链接到 node_modules。
  • 创建一个非扁平的 node_modules 目录
  • 软连接、硬链接
    • 软连接:一个“指路牌”,指向文件的路径,但它不是真实文件。
    • 硬链接:硬链接是给文件起两个名字,两个名字“是真实的”,都能直接访问文件内容。
        1. 硬连接只能创建文件的链接,不能链接文件夹(目录)
        1. 硬连接不能跨文件系统:不能让C盘的文件在D盘有另外一个名字
        1. 硬连接是指向 inode(相同内容):inode 其实就是操作系统给文件的“身份证”,里面有文件的各种信息(权限、修改时间、数据位置等),硬连接就是创建了一个新的“文件名”指向和原文件一样的 inode。
    • 参考:nodejs 创建硬链接和软链接

pnpm store

pnpm 安装的包真实存储位置是一个全局的内容寻址存储库 实际地址在,例如:D:\.pnpm-store\store\v3 包的实际文件是根据内容哈希进行存储的

bash
pnpm store path # 查看当前的 store 位置
pnpm config set store-dir <path> #自定义 store 位置

pnpm command 官方连接

pnpm install

bash
pnpm install

content-addressable storage 默认情况下,pnpm 使用一种名为 "content-addressable storage" 的方式来管理依赖

  • 全局存储: pnpm 会将所有依赖包存储在一个全局的 content-addressable 存储中。 每个包只存储一次,即使多个项目依赖于同一个包的不同版本。
  • 硬链接和符号链接: 在 node_modules 目录中,pnpm 使用硬链接和符号链接来创建依赖关系。 这使得 node_modules 目录非常轻量级,并且避免了重复安装相同的依赖包。
  • 非扁平结构: pnpm 创建的 node_modules 目录结构不是扁平的。 每个依赖包只能够访问它在 package.json 文件中声明的依赖项。 这可以防止 "phantom dependencies"(幻影依赖)问题,即代码意外地依赖于未在 package.json 文件中声明的依赖项。
bash
pnpm install --shamefully-hoist
  • 会将所有依赖包都提升到 node_modules 目录的顶层,使得所有依赖包都可以被项目中的任何模块访问
  • 使用场景(迁移现有项目): 如果你正在将一个使用 npm 或 yarn 的现有项目迁移到 pnpm,使用 --shamefully-hoist 标志可以更容易地完成迁移,因为它会创建一个与 npm 或 yarn 类似的 node_modules 结构。
bash
# 恢复到执行 pnpm install --shamefully-hoist 之前的状态
rm -rf node_modules
rm pnpm-lock.yaml
pnpm store prune  # 或者 rm -rf $(pnpm store path) 会删除未被任何项目引用的包
pnpm install

pnpm remove

Aliases: rm, uninstall, un

bash
pnpm remove
  • pnpm link <dir> 和 pnpm link --dir <dir> 之间的区别​
bash
# The current directory is foo
# 在一个项目下,link到外部的包
pnpm link ../bar
- foo
  - node_modules
    - bar -> ../../bar
- bar
# The current directory is bar
# 在包目录下,将包连接到外部
pnpm link --dir ../foo
- foo
  - node_modules
    - bar -> ../../bar
- bar
  • global
bash
cd ~/projects/foo
pnpm install # install dependencies of foo
pnpm link --global # link foo globally
cd ~/projects/my-project
pnpm link --global foo # link foo to my-project
  • unlink
bash
# 项目内的所有链接依赖项都将取消链接
pnpm unlink
# pnpm在删除外部链接后重新安装依赖项
# 如果pckageName在package.json 中会重新安装
pnpm unlink <package>
# 彻底删除
pnpm uninstall --global <package>

pnpm update

bash
# 更新所有依赖项,遵守 package.json 中指定的范围
pnpm up
# 将所有依赖项更新到最新版本
pnpm up --latest
# v2 的最新版本
pnpm up foo@2

pnpm --filter

多包仓库中精确地控制命令的执行范围

bash
pnpm --filter "@babel/core" test # 只执行@babel/core包的test
pnpm --filter "@babel/*" test
pnpm --filter "*core" test

给子包安装依赖

bash
# 给子包foo安装lodash
pnpm add lodash -D --filter foo
# 当你添加依赖是 workspace 里已有的包时,可以使用 workspace 协议:
pnpm add foo@workspace:* --filter bar

pnpm patch / pnpm patch-commit / pnpm patch-remove

给软件包添加补丁

bash
pnpm patch express@^5.1.0
pnpm patch-commit ./node_modules/.pnpm_patches/express@^5.1.0
pnpm patch-remove foo@1.0.0 bar@1.0.1

pnpm store prune

bash
# 清理 pnpm 存储区(store)中当前项目不再使用的包缓存
pnpm store prune

pnpm-workspace.yaml

pnpm-workspace.yaml 文件用来定义哪些子包属于工作区

yaml
packages:
  # 指定根目录直接子目录中的包
  - 'my-app'
  # packages/ 直接子目录中的所有包
  - 'packages/*'
  # components/ 子目录中的所有包
  - 'components/**'
  # 排除测试目录中的包
  - '!**/test/**'
# 定义目录和依赖版本号
catalog:
  react: ^18.3.1
  redux: ^5.0.1
json
{
  "name": "@example/app",
  "dependencies": {
		"foo": "workspace:*",  // "foo": "1.5.0",
		"bar": "workspace:~", //"bar": "~1.5.0",
		"qar": "workspace:^", //"qar": "^1.5.0",
		"zoo": "workspace:^1.5.0" //"zoo": "^1.5.0"
	},
  "dependencies": {
    "react": "catalog:",
    "redux": "catalog:"
  }
}
写法作用说明版本匹配规则
"foo": "workspace:*"依赖 foo 包的任何版本,始终指向工作区里的本地包的最新版本,不管版本号是多少。允许接受主版本、次版本和补丁版本的任何更新。
"bar": "workspace:~"依赖符合 ~ 范围的工作区版本,匹配同一主版本和次版本号,补丁版本允许更新。匹配 ~x.y.z 语义,允许补丁版本升级,不允许次版本和主版本升级。
"qar": "workspace:^"依赖符合 ^ 范围的工作区版本,允许次版本和补丁版本更新,主版本必须一致。匹配 ^x.y.z 语义,允许次版本和补丁版本更新,不允许主版本升级。
"zoo": "workspace:^1.5.0"依赖版本范围明确为 ^1.5.0 的工作区包版本,维护该语义版本范围的依赖更新。版本范围 >=1.5.0 <2.0.0,兼容 1.x 版本的升级