pnpm

pnpm(performant npm) 通过软、硬链接(hark link、symbolic link) + 全局存储(store)结合的依赖管理方式完全实现了依赖树结构的包管理方式,解决了 npm3 及 yarn 中的幽灵依赖和 npm 分身的问题,提升了依赖包的安装速度,减小了磁盘空间占用。

pnpm 为何节省空间

它解决了 npm/yarn 平铺 node_modules 带来的依赖项重复的问题 (doppelgangers)

假设存在依赖依赖:

1
2
3
4
5
6
7
8
9
.
├── package-a
│ └── lodash@4.0.0
├── package-b
│ └── lodash@4.0.0
├── package-c
│ └── lodash@3.0.0
└── package-d
└── lodash@3.0.0

那么不可避免地在 npm 或者 yarn 中,lodash@3.0.0 会被多次安装,无疑造成了空间的浪费与诸多问题。

1
2
3
4
5
6
7
./node_modules/lodash
./node_modules/package-a
./node_modules/package-b
./node_modules/package-c
./node_modules/package-c/node_mdoules/lodash
./node_modules/package-d
./node_modules/package-d/node_mdoules/lodash

这是一个较为常见的场景,在平时项目中有些库相同版本甚至会安装七八次,如 postcssansi-stylesansi-regexbraces 等,可以去yarn.lock/package-lock.json 中搜索一下。

而在 pnpm 中,它改变了 npm/yarn 的目录结构,采用软链接的方式,避免了 doppelgangers 问题更加节省空间。

它最终生成的 node_modules 如下所示,从中也可以看出它解决了幽灵依赖的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
./node_modules/package-a       ->  .pnpm/package-a@1.0.0/node_modules/package-a
./node_modules/package-b -> .pnpm/package-b@1.0.0/node_modules/package-b
./node_modules/package-c -> .pnpm/package-c@1.0.0/node_modules/package-c
./node_modules/package-d -> .pnpm/package-d@1.0.0/node_modules/package-d
./node_modules/.pnpm/lodash@3.0.0
./node_modules/.pnpm/lodash@4.0.0
./node_modules/.pnpm/package-a@1.0.0
./node_modules/.pnpm/package-a@1.0.0/node_modules/package-a
./node_modules/.pnpm/package-a@1.0.0/node_modules/lodash -> .pnpm/package-a@1.0.0/node_modules/lodash@4.0.0
./node_modules/.pnpm/package-b@1.0.0
./node_modules/.pnpm/package-b@1.0.0/node_modules/package-b
./node_modules/.pnpm/package-b@1.0.0/node_modules/lodash -> .pnpm/package-b@1.0.0/node_modules/lodash@4.0.0
./node_modules/.pnpm/package-c@1.0.0
./node_modules/.pnpm/package-c@1.0.0/node_modules/package-c
./node_modules/.pnpm/package-c@1.0.0/node_modules/lodash -> .pnpm/package-c@1.0.0/node_modules/lodash@3.0.0
./node_modules/.pnpm/package-d@1.0.0
./node_modules/.pnpm/package-d@1.0.0/node_modules/package-d
./node_modules/.pnpm/package-d@1.0.0/node_modules/lodash -> .pnpm/package-d@1.0.0/node_modules/lodash@3.0.0

如此,依赖软链接的方式,可解决重复依赖安装 (doppelgangers) 的问题,如果一个项目占用 1000 MB,那么使用 pnpm 可能仅占用 800 MB

然而它除此之外,还有一个最大的好处,如果一个项目占用 1000 MB,传统方式十个项目占用 10000 MB,那么使用 pnpm 可能仅占用 3000 MB,而它得益于硬链接。

再借用以上示例,lodash@3.0.0lodash@4.0.0 会生成一个指向全局目录(~/.pnpm-store)的硬链接,如果新项目依赖二者,则可复用存储空间。

1
2
./node_modules/.pnpm/lodash@3.0.0/node_modules/lodash   -> hardlink
./node_modules/.pnpm/lodash@4.0.0/node_modules/lodash -> hardlink

pnpm、yarn、npm 的区别

功能 pnpm yarn npm
工作空间支持(monorepo) O O O
隔离的 node_modules O-默认 O O
提升的 node_modules O O O-默认
自动安装 peers O X O
Plug’n’Play O O-默认 X
零安装 X O X
修补依赖项 O O X
管理 Node.js 版本 O X X
有锁文件 O-pnpm-lock.yaml O-yarn.lock O-package-lock.json
支持覆盖 O O-通过 resolutions O
内容可寻址存储 O X X
动态包执行 O-通过 pnpm dlx O-通过 yarn dlx O-通过 npx
Side-effects cache O X X
Listing License O-Vua pnpm licenses list O-Via a plugin X

安装和使用

1
2
3
4
5
6
7
8
npm install -g pnpm

pnpm -v

//使用
pnpm init
pnpm install xxx
pnpm run xxx
1
2
3
4
5
6
7
8
9
10
11
12
//设置源
pnpm config get registry
pnpm config set registry <淘宝源或其他源地址>

//管理依赖
pnpm add xxx
pnpm add -D xxx //安装到devDependencies
pnpm add -O xxx //安装到optionalDependencies
pnpm add -g xx //全局安装依赖包
pnpm install 或(pnpm i)
pnpm update 或( pnpm up)
pnpm remove 或(pnpm rm/uninstall/un)

查看依赖

1
2
3
pnpm list 或(pnpm ls)
pnpm list --global 或(pnpm ls --g)
pnpm outdated //检查过期的依赖

运行脚本

1
2
3
4
pnpm run xxx 或(pnpm xxx)
pnpm test 运行test测试脚本
pnpm create
pnpm start

包存储 store

pnpm store: pnpm 资源在磁盘上的存储空间

可以通过 pnpm store path命令查看 store 存储目录的路径

在项目中执行pnpm install的时候,依赖包存在于 store 中,直接创建依赖包硬链接到 store 中,如果不存在,则从远程下载后存储在 store 中,并从项目的 node_modules 依赖包中创建硬链接到 store 中。

pnpm store prune,从存储中删除未引用的包。

workspce 协议

workspce:工作空间

workspace:*协议,表示依赖的就一直是本地的包,而不是从npm registry安装的包。

别名引用

假如工作区有一个名为 foo 的包,可以通过这样引用 "foo": "workspace:",如果是其它别名,可以这么引用:"bar": "workspace:foo@*"

相对引用

假如 packages 下有同层级的 foo、bar,其中 bar 依赖于 foo,则可以写作"bar": "workspace:../foo"

发布 workspace 包

当 workspace 包打包发布时,将会动态替换这些 workspace:依赖。

npm 或 Yarn 转 pnpm

操作步骤:

  1. 全局安装 pnpm

npm install -g pnpm

  1. 删除 npm 或 yarn 生成的 node_modules
1
2
# 项目目录下运行或手动物理删除
rm -rf node_modules
  1. pnpm import 从其他软件包管理器的 lock 文件生成  pnpm-lock.yaml,再执行 pnpm install --frozen-lockfile(相当于 npm ci)生成依赖,防止没有 lock 文件意外升级依赖包,导致项目出错
1
2
3
4
5
# 生成`pnpm-lock.yaml`
pnpm import

# 安装依赖
pnpm install --frozen-lockfile
  1. 删除 npm 或 yarn 生成的 lock 文件
1
2
3
4
5
# 删除 package-lock.json
rm -rf package-lock.json

# 删除 yarn.lock
rm -rf yarn.lock

项目中的 npm 命令等修改为 pnpm,包括 README 文档、运行命令等

参考链接