包管理器简史
npm 3 之前
node_modules 结构是有序的
假设有如下的依赖项:
.
├── package-a
│ └── lodash
├── package-b
│ └── lodash
├── package-c
│ └── lodash
└── package-d
└── lodash
node_modules 结构如下:
node_modules
├── package-a
│ └── node_modules
│ └── lodash
├── package-b
│ └── node_modules
│ └── lodash
├── package-c
│ └── node_modules
│ └── lodash
└── package-d
└── node_modules
└── lodash
上面结构有 2 个严重的问题:① 会创建过深的依赖树、② 会重复安装依赖包
npm 3+ & yarn
为了解决上述的两个问题,扁平化 node_modules
假设有如下的依赖项:
.
├── package-a
│ └── lodash
├── package-b
│ └── lodash
├── package-c
│ └── lodash
└── package-d
└── lodash
在 npm 3+ 和 yarn 中,node_modules 结构变成:
node_modules
├── package-a
├── package-b
├── package-c
├── package-d
├── lodash
扁平化 node_modules 简化了依赖树的结构,也减少了 node_modules 的体积
但扁平化 node_modules 后仍存在一些问题:
① 仍可能会创建过深的依赖树、重复安装依赖包
假设存在依赖项:
.
├── package-a
│ └── lodash@4.0.0
├── package-b
│ └── lodash@4.0.0
├── package-c
│ └── lodash@3.0.0
└── package-d
└── lodash@3.0.0
node_modules 结构变成:
node_modules
├── package-a
├── package-b
├── package-c
│ └── node_modules
│ └── lodash
├── package-d
│ └── node_modules
│ └── lodash
├── lodash
这里只有一个版本的依赖会被提升,具体哪个依赖被提升需要看哪个依赖先被下载
② 扁平化 node_modules 会导致 “幽灵依赖” 问题
假设有如下的依赖项:
.
├── package-a
├── package-b
├── package-c
└── package-d
└── lodash
扁平化 node_modules 后,node_modules 的结构变成:
node_modules
├── package-a
├── package-b
├── package-c
├── package-d
├── lodash
因为使用的是扁平化的 node_modules 结构,使开发者有机会使用未在 package.json 中声明的依赖包
比方说,我下载 express,那 express 所依赖的包,都会下载到与 express 同一层级的目录下。假设,我现在要使用 express 所依赖的包中的 body-parser,我不需要再
npm i body-parser
便可直接使用。这会出现一个问题,项目的 package.json 中没有设置 body-parser 的依赖。
如果某一天,express 升级了,不依赖 body-parser 了,但项目中还用到 body-parser,此时项目就会报错;或者说,某一天依赖的 body-parser 的版本升级了,但项目中用到的还是旧版本的 API,项目也会报错。
依赖安装原理
对于未被完全解决的问题:① 会创建过深的依赖树、② 会重复安装依赖包,以及扁平化 node_modules 导致的新问题 “幽灵依赖”,pnpm 使用 [软链接] + [硬链接] + [内容寻址存储] 解决
硬链接 & 软链接
在 Linux 的文件系统中,保存在磁盘分区中的文件都有一个编号,称为 [索引节点号] Inode Index
-
硬链接与源文件指向同一个物理地址,具有相同的 Inode Index;硬连接的作用:允许一个文件拥有多个有效路径名,这样用户就可以给重要文件建立硬链接,以防 “误删”。就是说,只删除一个链接并不影响源文件和其它的链接,只有最后一个链接被删除后,文件的数据块才会被释放
-
**软链接(符号链接)**相当于一个快捷方式;在软链接中,文件实际上是一个文本文件,文件内容是另一个文件的位置信息
可以在 Virtual x86 上面模拟 Linux 命令行操作:
① 创建源文件及其软硬链接
root@superman:~/link_test# vi link
root@superman:~/link_test# cat link
superman
root@superman:~/link_test# ln link link-hard
root@superman:~/link_test# ln -s link link-soft
root@superman:~/link_test# ls -li
total 8
1443783 -rw-r--r-- 2 root root 9 Aug 13 12:01 link
1443783 -rw-r--r-- 2 root root 9 Aug 13 12:01 link-hard
1443782 lrwxrwxrwx 1 root root 4 Aug 13 12:02 link-soft -> link
② 通过软硬连接访问源文件
root@superman:~/link_test# cat link-hard
superman
root@superman:~/link_test# cat link-soft
superman
③ 修改源文件,查看软硬链接
root@superman:~/link_test# vi link
root@superman:~/link_test# cat link
superman monster
root@superman:~/link_test# cat link-hard
superman monster
root@superman:~/link_test# cat link-soft
superman monster
④ 删除源文件,查看软硬链接
root@superman:~/link_test# rm link
root@superman:~/link_test# ls -li
total 4
1443783 -rw-r--r-- 1 root root 17 Aug 13 12:02 link-hard
1443782 lrwxrwxrwx 1 root root 4 Aug 13 12:02 link-soft -> link
root@superman:~/link_test# cat link-hard
superman monster
root@superman:~/link_test# cat link-soft
cat: link-soft: No such file or directory
内容寻址存储
内容寻址存储(Content Addressable Storage,CAS)是一种存储和检索数据的方法。
CAS 基于数据的内容计算出唯一的哈希值,并将其用作数据的索引。因此,相同内容的数据块将始终具有相同的哈希值,可以通过哈希值快速检索数据。
依赖安装
假设存在依赖项:
.
├── package-a
│ └── lodash@4.0.0
├── package-b
│ └── lodash@4.0.0
├── package-c
│ └── lodash@3.0.0
└── package-d
└── lodash@3.0.0
在首次安装依赖包时,pnpm 会将依赖包下载到一个公共目录 store 中。
在下一次安装依赖包时,pnpm 就会从公共目录 store 中找到已安装的依赖包,并创建硬链接到项目中。
使用 pnpm 下载,所有的依赖包会平铺在 node_module/.pnpm 目录下;
因为使用了 CAS 技术,所以不同版本号的依赖包可以区别开来。
然后,pnpm 会在 node_modules 目录下创建一级依赖包的软链接:
node_modules
├── .pnpm
│ └── package-a (hard-link to store)
│ └── package-b (hard-link to store)
│ └── package-c (hard-link to store)
│ └── package-d (hard-link to store)
│ └── lodash@3.0.0 (hard-link to store)
│ └── lodash@4.0.0 (hard-link to store)
├── package-a (soft-link to ./.pnpm/package-a)
├── package-b (soft-link to ./.pnpm/package-b)
├── package-c (soft-link to ./.pnpm/package-c)
├── package-d (soft-link to ./.pnpm/package-d)
注意,此时 .pnpm 的结构不是扁平化的:
node_modules
├── .pnpm
│ └── package-a
│ │ └── node_modules
│ │ └── lodash (soft-link to ../../lodash@4.0.0)
│ └── package-b
│ │ └── node_modules
│ │ └── lodash (soft-link to ../../lodash@4.0.0)
│ └── package-c
│ │ └── node_modules
│ │ └── lodash (soft-link to ../../lodash@3.0.0)
│ └── package-d
│ │ └── node_modules
│ │ └── lodash (soft-link to ../../lodash@3.0.0)
│ └── lodash@3.0.0
│ └── lodash@4.0.0
├── package-a
├── package-b
├── package-c
├── package-d
管理 Node 版本
pnpm 与其他 npm 包管理工具不同的是,pnpm 可以管理 Node 版本。
注意:如需使用 pnpm 管理 Node 版本,需要删除任何由其他工具安装的 Node,然后使用 pnpm - Installation 上提供的独立脚本来安装 pnpm
执行脚本
-
安装并使用 LTS 版本的 Node:
pnpm env use -g lts
安装并使用 v16 版本的 Node:
pnpm env use -g 16
-
移除指定版本的 Node:
pnpm env rm --global 14.0.0
-
输出本地安装的版本:
pnpm env ls
输出远程可用的版本:
pnpm env ls --remote
输出远程可用的 Node 16 版本:
pnpm env ls --remote 16
执行 pnpm env use -g xxx
时,可能会抛出如下错误:
-
ERROR: Unable to find the global bin directory
:
要先执行pnpm setup
;pnpm setup
会:① 为 pnpm CLI 创建一个主目录、② 通过更新 shell 配置文件将 pnpm 主目录添加到 PATH、③ 将 pnpm 可执行文件复制到 pnpm 主目录 -
PERM: operation not permitted, symlink XXX
:
需要以管理员身份执行
配置 .npmrc
在配置文件 .npmrc 中配置 use-node-version
可指定项目运行时应用的 Node 版本,支持 semver 版本设置规范。设置后, pnpm 将自动安装指定版本的 Node 并将其用于执行 pnpm run
/ pnpm node
命令
use-node-version=^16.x
配置项目
项目迁移
pnpm import
可基于其它类型的 lock 文件生成 pnpm-lock.yaml 文件,目前支持:
-
package-lock.json
-
npm-shrinkwrap.json
-
yarn.lock
只允许 pnpm
当您在项目中使用 pnpm 时,如果不希望被其他人意外运行 npm i
或 yarn
,可在 package.json 中配置 pnpm 的生命周期脚本 preinstall
:
{
"scripts": {
"preinstall": "npx only-allow pnpm"
}
}
现在,只要有人运行 npm i
或 yarn
,就会发生错误并且不会继续安装。
脚本 preinstall
仅在执行 pnpm install
时运行,会在安装任何依赖项之前运行
扁平化处理
使依赖及其子依赖都平铺到 node_modules 目录下,有如下 3 种方法:
① 配置 .npmrc:
shamefully-hoist=true
② 执行 pnpm install --shamefully-hoist
③ 配置 package.json:
{
// ...
"pnpm": {
"shamefully-hoist": true
}
}
基础语法
-
安装 pnpm:
npm i pnpm -g
-
查看版本:
pnpm -v
常用命令
-
pnpm i
-
pnpm i xxx
/pnpm add xxx
-
pnpm rm xxx
/pnpm un xxx
-
pnpm run xxx
/pnpm xxx
其他命令
-
pnpm init
-
pnpm ls
-
pnpm update
/pnpm up
-
pnpm prune
:移除未被使用的依赖包pnpm store prune
:移除 pnpm 的全局存储中未被使用的依赖包
配置 pnpm
-
pnpm config ls
:查看 pnpm 配置 -
pnpm config get registry
:查看镜像pnpm config set registry xxx
:修改镜像 -
pnpm config get store-dir
:查看 pnpm 的全局存储目录pnpm config set store-dir ~/.my-pnpm-store
:修改 pnpm 的全局存储目录
如果未配置 pnpm 的全局存储目录,则 pnpm 会在同一硬盘上自动创建一个存储。
注意:项目应该与 store 在同一个磁盘上,否则 pnpm 会将依赖包复制到项目中,而不是使用硬链接(这是因为硬链接不能跨磁盘使用)。项目仍然会保持 pnpm 的优势,但是每个驱动器可能有多余的包。
在安装依赖项时,pnpm 与 npm 使用相同的配置。如果你为 npm 配置了一个私有的软件包注册表, pnpm 也能够获得授权请求,并且无需任何额外的配置。
错误处理
pnpm: 无法加载文件 C:\XXX\pnpm.ps1
,因为在此系统上禁止运行脚本。
-
win + s 在系统中搜索框输入 “PowerShell” 右击 “管理员身份运行”
-
输入 “set-ExecutionPolicy RemoteSigned” 回车,根据提示输入 A,回车
-
再次 pnpm -v 执行成功
不只是 pnpm 命令,包括 cnpm、yarn 等这些命令,如果执行时报这样的错误,都可以通过此方法解决。
set-ExecutionPolicy RemoteSigned 是一个 PowerShell 命令,它用于设置 PowerShell 的执行策略。执行策略是一种安全机制,它决定了你是否可以运行 PowerShell 脚本,以及这些脚本是否需要被数字签名。set-ExecutionPolicy RemoteSigned 的意思是,你可以运行你自己写的或者从本地计算机上获取的未签名的脚本,但是如果是你从网络上获取的脚本,它们必须被可信任的发布者签名,否则无法运行。这样可以防止你运行一些恶意的或者不安全的脚本。