performant npm,高性能的 npm,由 npm、yarn 衍生而来,解决了 npm/yarn 内部潜在的 bug,极大的优化了性能。
特点
速度快,安装高效
节省磁盘空间
当使用 npm 或 yarn 时,如果有 100 个项目使用到某个依赖(dependency),就会有 100 份改依赖的副本保存在硬盘上。使用 pnpm 时,依赖会被存储在内容可寻址的存储中(the denpendency wil be sotred in a content-addressable store),所有
- 用到某个依赖的不同版本,只会将不同版本有差异的文件添加到仓库
- 所有的文件都会存储在硬盘的某一个位置。当软件包被安装时,包里的文件会硬链接到该位置,不会再占用额外的磁盘空间。可允许跨项目的共享同一版本的依赖。
优化了依赖的 node_modules 结构,创建非扁平化的 node_modules 文件夹
- 使用 npm 或 yarn 安装依赖项时,所有的包都被提升到模块目录的根目录,因此,项目可以访问到未被添加进当前项目的依赖
- 默认情况下,pnpm 使用软链的方式将项目的直接依赖添加进模块文件夹的根目录
内置了对 Monorepo 的支持,无需在引入 lerna
npm、yarn 面临的问题
Npm 安装包的过程
- 先检查
.npmrc
文件,包含以下 npm 配置信息,如 registry,全局缓存目录等 - 读取 package.json 文件中的依赖信息,根据semver语义化版本信息生成完整的版本依赖树
- 先查询本地缓存目录,如果有缓存就直接使用,如果不存在,再去 npm 仓库下载到的缓存目录,此过程,会校验包的哈希,以保证安全性
上述过程中依赖树可能存在大量重复的模块,因为它按依赖树的结构进行安装,比如 A,B 模块都依赖了 C 模块,那么 C 模块会在 A、B 模块的 node_modules 目录内重复安装,造成大量的重复和冗余。
npm3 进行了优化,加入了dedupe模块扁平化
,尽可能的将所有依赖都发到最顶层node_modules 目录下,如果有重复的模块,且版本相互兼容,就会只保留一个,丢弃其它的。如果版本不兼容,那么只有一个被提升到顶层,其它的会放在其父依赖的 node_modules 目录下,而哪一个被提升到顶层可能不固定,所有在 npm5+版本中新增了package-lock.json
用于锁定依赖结构,确保每一次安装出来的目录结构和依赖版本相同。
phantom dependencies (幽灵依赖)
由于扁平化的处理方式, 用户可以引用 package.json 中没有声明的依赖。 比如项目 1 使用 依赖 A,其中 A 有一个 A1 包,由于扁平化 A1 包会别提升到项目 node_modules 下,此时在项目 1 中可直接使用 A1 包,将来 A 升级或不在使用 A1 包,那么项目 1 就会出现异常或报错
npm dopplelgangers (npm 包分身)
npm 扁平化的处理机制,可能导致心魔中应用多个版本的包,从而导致
- 需要安装多个版本包
- 打包出来的文件可能包含多个包
- 如果该包需要用到单例,会出现异常
pnpm 原理
pnpm 安装依赖后 node_modules 目录大概如下:
其中 node_modules 包含 .bin
,.pnpm
目录以及其它的 npm 包,这些 npm 包与 package.json 中声明的保持一致,因此只有 package.json 中声明过的依赖才能在项目中使用,从而避免了幽灵依赖的问题。
不同于 npm,这些 npm 包都是 symbolic link 符号链接,指向了.pnpm 目录下的包
.pnpm 下的包名规则是
1 | <organization-name> + <package-name>@<version>/node_modules/<name> |
因为.pnpm 里面的包路径中加入了版本号信息,因此可以在同一目录下相对扁平化的存储所有的包,
由于每个包又有自己的依赖,为了迎合 node.js 的包查找规则,因此上面的包命名规则中有添加了一层 node_modules 目录,本包的依赖也会在这个目录 内创建 Symbolic link 符号链接,链接到.pnmp 顶层上的包,通过此法,使得每一个包都能正常的使用自己的依赖,但不会污染到顶层的 node_modules,从而避免幽灵依赖的问题
注: node_modules 目录下还存在一部分没有在 package.json 中声明的依赖,比如
@jest/type
,@vitejs
,这是因为一部分包需要在顶层才能使用,比如 eslint、typescript 声明,prettier 插件等,pnpm 会默认将包名含 types,eslint、prettier 等关键词的包提升到顶层。可通过.npmrc
中设置public-hoist-pattern[]=
来关闭这些提升
Hard Link 和 Store
在 linux 文件系统中,保存在磁盘分区中文件都会分配一个编号,称为索引编号(inode index),文件名和 inode 通常是一一对应的,且允许多个文件名指向同一个 inode, 删除任何一个文件名并不会对 inode 或其它文件有影响。只有当最后一个硬链接删除后才回收 inode 编号并标记对应的 block 为可用,等待其它数据存储后抹去其内容。如此,同一个文件可以有多个文件路径和文件名,但在磁盘中仅仅只有一份内容,避免了重复占用。
项目的 node_modules 目录下的所有文件都是通过硬链接的方式链接到全局 pnpm store 内的文件,以.pnpm 目录下的 vue 为例, 查看 README.md 文件的 inode 编号:64817529
进入 pnpm 全局 stroe 路径,查找 inode 编号
1 | Users/liuzhenjiang948/.pnpm-store // 全局目录 |
所有 npm 包里面的所有文件都会在全局进行存储,存储是根据文件的哈希信息进行散列,这样可以扁平化的存储,而不用按原始的 npm 包目录结构进行存储,节省大量磁盘空间。
pnpm install 安装包的大致过程
- 判断是否有
pnpm-lock.yaml
文件,- 如果没有,则根据
package.json
中声明的版本计算依赖树以及各版本 npm 包的 integrity 值 - 如果有 且版本跟 package.json 中声明的匹配,就根据
pnpm-lock.yaml
中各个依赖包的 integrity 信息,并计算对应的 -index.json 文件的完整哈希和路径 - 如果 stroe 里面有对应的包的-index.json 文件,即有该包的缓存
- 如果没有的话需联网下载对应的 tar 包,并生成对应的-index.json 文件,并且将包内的文件计算 integrityz 值和哈希
- 对整个依赖树进行完以上操作后再项目内的 node_modules 目录创建个依赖的符号链接和文件的硬链接完成安装
- 如果没有,则根据
CLI 命令
pnpm add
, -D 安装到 devDependencies
,-O 安装到 optionalDependencies
pnpm install (i), 安装项目所有依赖
1
2
3Packages are copied from the content-addressable store to the virtual store.
Content-addressable store is at: /Users/liuzhenjiang948/Library/pnpm/store/v3
Virtual store is at: node_modules/.pnpm配置项 默认值/类型 说明 –offline false 为 true,仅使用在 store
中已有的包,本地找不到,安装失败–ignore-scripts false 不执行任何项目中 package.json
和它依赖项中定义的任何脚本–ockfile-only fasle 只更新 pnpm-lock.yaml
和package.json
,不写入node_modules
目录–fix-lockfile 自动修复损坏的 lock 文件入口 —reporter= default, silent, append-only, ndjson silent: 除致命 errors,不输出记录信息
ndjson: 最详细记录信息pnpm update (up), 更新软件包的最新版本
命令,配置项 说明 pnpm up 按 package.json
指定的范围更新所有的依赖项pnpm up –latest 更新所有依赖项,忽略 package.json
指定的范围pnpm up –recursive 递归更新子目录中的依赖包 pnpm up –global 更新全局安装的依赖包 pnpm remove (rm,un,uninstall), 删除指定的包
pnpm link (ln), 使当前本地包 可在系统范围内 或 其他位置 访问
在项目开发时,需要将一些公用的代码抽离发布成 npm 包,作为项目的依赖去安装使用。但在开发调试中需要频繁的打包发布,再安装依赖,很不方便。为解决此问题,可以使用 link 命令将模块链接到项目中。
- 假设 项目名 project-jiang,和一个公用组件模块 common,现在需要在项目中使用 common,且 common 是作为项目的 npm 包依赖。
- 在 common 目录下使用
pnpm link
,将 common 模块创建成本地依赖包 - 在 project-jiang 项目中,使用
pnpm link common
和本地 common 模块建立链接。此时该项目中的 node_modules 里就会添加一个 common 模块的软连接
pnpm unlink, 取消链接一个系统访问的 package
pnpm import, 从另一个软件包管理器的 lock 文件生产
pnpm-lock.yaml
, 支持的源文件package-lock.json
,yarn.lock
,npm-shrinkwrap.json
查看依赖
- pnpm audit, 检查已安装包的已知安全问题,如果发现问题,尝试使用
pnpm update
,pnpm audit --fix
- pnpm list, 以树形结构输出所有的已安装
package
的版本及其依赖 - pnpm outdated, 检查过期的 packages
运行脚本
pnpm run , 运行一个在 package 文件定义的脚本
pnpm test, 运行在
package
的scripts
对象中test
属性指定的任意的命令pnpm exec, 在项目访问内执行 shell
pnpm dlx, 从源中获取包而不将其安装为依赖,热加载,并运行它公开的任何默认命令的二级制文件
例如
pnpm dlx create-react-app my-app
, 使用create-react-app
来初始化一个 react 应用pnpm create, 从
create-*
或@foo/create-*
启动套件创建项目, 例如pnpm create react-app my-app
管理 Node 环境
pnpm env
安装并使用指定版本 node.js
pnpm env use --global lts
,pnpm env use --global 16
,pnpm env use --global latest