pnpm 实践分享

4,615 阅读7分钟

1. 优势

1.1 节省磁盘空间

下面是 pnpm 官网的介绍

image.png 使用 npm 时,如果你有 100 个项目,并且所有项目都有一个相同的依赖包,那么, 你在硬盘上就需要保存 100 份该相同依赖包的副本

如果是使用 pnpm,依赖包将被存放在一个统一的位置。每一个文件都单独进行保存到仓库

如果发布了一个新版本,并且新版本中只有一个文件有修改,则 pnpm update 只需要添加一个新文件到存储中,而不会因为一个文件的修改而保存依赖包的所有文件

1.2 更好的 node_modules 管理

仅将项目的直接依赖项添加到 node_modules 的根目录下 , 下面是使用 pnpm 安装 vue 以后 node_modules 显示的内容,可以看见node_modules 下面只有vue,而 vue 依赖的第三方包都在 .pnpm 下面,这样只有真正处于依赖关系的包才可以访问

考虑这样一个场景:

假如第三方包安装了axios,如果这个axios在node_modules里面,那么开发者可以直接通过import引用。那假如说第三方包在某次升级中,把axios给去掉了。那么在使用开发的时候就将出现问题。

关于node_modules更详细的内容会在后面进行描述

2. 安装配置

要求:node > v16.14

2.1 安装

npm install pnpm -g

2.2 .npmrc

  1. pnpm 的配置文件同 npm 一样,都是 .npmrc,也就是说对 npm 的设置默认对 pnpm 生效

  2. 要像 npm 那样组织node\modules,需要在.npmrc里面设置 hoist=true

  3. 锁文件 pnpm-lock.yaml

2.2.1 pnpm 关键的配置
  1. store-dir 仓库地址
-   Default:

    -   If the  $PNPM_HOME env variable is set, then  $PNPM_HOME/store
    -   If the  $XDG_DATA_HOME env variable is set, then  $XDG_DATA_HOME/pnpm/store
    -   On Windows:  ~/AppData/Local/pnpm/store
    -   On macOS:  ~/Library/pnpm/store
    -   On Linux:  ~/.local/share/pnpm/store

相关命令

pnpm store path 直接查询全局存储路径
pnpm config get store-dir 查询的是配置文件中的存储路径设置
pnpm config set store-dir /path/pnpm/store -g 设置仓库存储路径 

2. global-dir 全局安装路径

-   Default:

    -   If the  $XDG_DATA_HOME env variable is set, then  $XDG_DATA_HOME/pnpm/global
    -   On Windows:  ~/AppData/Local/pnpm/global
    -   On macOS:  ~/Library/pnpm/global
    -   On Linux:  ~/.local/share/pnpm/global

相关命令

pnpm config get global-dir
pnpm config set global-dir /path/pnpm/global -g 

当使用 npm add A -g 安装全局包,安装的内容也会在 store 里面

3. global-bin-dir 全局可执行文件的路径

-   Default:

    -   If the  $XDG_DATA_HOME env variable is set, then  $XDG_DATA_HOME/pnpm
    -   On Windows:  ~/AppData/Local/pnpm
    -   On macOS:  ~/Library/pnpm
    -   On Linux:  ~/.local/share/pnpm

相关命令

pnpm config get global-bin-dir
pnpm config set global-bin-dir /path/pnpm -g  

设置完毕以后,使用类似于 pnpm add eslint -g 时,对应的可执行文件,会安装到这个目录里面

2.4 pnpm add xxx -g

如果执行 pnpm add xxx -g 出现下面的报错 image.png

  1. 首先我们可以执行 pnpm setup,执行完成后会在 ~ 下面生成 .bashrc,内容如下
    # pnpm
    export PNPM_HOME="/Users/tangfeng/Library/pnpm" 
    case ":$PATH:" in
      *":$PNPM_HOME:"*) ;;
      *) export PATH="$PNPM_HOME:$PATH" ;;
    esac
    # pnpm end

.bash_profile 会调用 .bashrc 里面的内容。也就是会把 bin 文件的默认地址(/Users/tangfeng/Library/pnpm)添加到环境变量 PATH 中。

  1. 如果按照上面的操作还有问题,那么就需要我们手动设置global-dirglobal-bin-dir,然后配置环境变量
    # 首先设置
    pnpm config set global-dir ~/pnpm/global -g
    pnpm config set global-bin-dir ~/pnpm -g

以 macos 为例

    # ~/.bash_profile
    export PATH=~/.npm-global/bin:~/pnpm:$PATH

~/pnpm 就是我们设置的 global-bin-dir,PATH 以 : 作为分割符,最后的 $PATH 代表的是系统默认设置的一些路径

注意: 设置完成以后执行 source ~/.bash_profile 才能生效

理解: 当我们执行类似于eslint xx的命令,会到环境变量对应的路径下找eslint的可执行文件

3. 命令

npmyarnpnpm描述
npm installyarn installpnpm install安装所有包
npm i pkgyarn addpnpm add pkg安装特定包
npm run cmdyarn cmdpnpm cmd/pnpm run cmd执行脚本
npm updateyarn upgradepnpm update/up/upgrade升级包
npm uninstallyarn removepnpm remove/rm/uninstall/un移除包
  1. pnpm install
    pnpm 安装的逻辑与 npm 并不完全相同,假如 package.json 的内容如下
"dependencies": {
    "axios-a": "^1.0.2"
  }

npm install 会默认安装 1.x.x 的最新版本,此时对应的就是 1.0.3,而 pnpm 会默认安装当前版本,也就是 1.0.2

  1. pnpm add

    • 默认安装在 dependencies-D 安装在 devDependencies
    • -g 全局
    • pnpm add sax@next, pnpm add sax@3.0.0 安装 sax 指定版本
  2. pnpm update

    • 当我们执行pnpm update axios-a时,更新逻辑符合 ^、~的意思
    • package.json 里面也会更新
    • pnpm up foo@2 更新到foo2.x的最新版本
    • pnpm up "@babel/*" 更新所有 @babel 相关的包
    • pnpm update !webpack 更新除 webpack 以外的所有包
  3. pnpm config

    #  配置相关设置
    npm/yarn/pnpm config set key value  
    npm/yarn/pnpm config get key  
    npm/yarn/pnpm config delete key  
    npm/yarn/pnpm config ls
    
  4. npm/yarn/pnpm init
    项目初始化

  5. npm/yarn/pnpm publish
    发布

  6. pnpm exec
    有点类似于 npx 指令,从 node_modules/.bin 找到可执行文件,然后执行(区别是pnpm exec 并不会尝试下载安装)

  7. pnpm create 通过模版创建新项目

    pnpm/npm/yarn create reat-app my-app
    npx create-react-app my-app
    

4. 深入理解 pnpm 的包管理

4.1 node_modules

  1. 当我们安装 axios-a 以后,node_modules 下面的 axios-a 只是一个软链接,

它链接的位置是 .pnpm/axios-a@1.0.3/node_modules/axios-a,.pnpm下面的包内容都包含了版本号

用一个 foo 引入了bar、bar引入了qar 的例子就是

node_modules

├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       ├── bar -> <store>/bar
    │       └── qar -> ../../qar@2.0.0/node_modules/qar
    ├── foo@1.0.0
    │   └── node_modules
    │       ├── foo -> <store>/foo
    │       ├── bar -> ../../bar@1.0.0/node_modules/bar
    └── qar@2.0.0
        └── node_modules
            └── qar -> <store>/qar

这样每一个包下面的 node_modules 就只会包含自己安装的那些包。解析模块时,node会忽略符号链接,直接去解析真实的位置

4.2 软链接、硬链接

  1. 索引结点,是一种文件目录瘦身策略。由于检索文件时只需用到文件名,因此可以将除了文件名以外的其他信息放到索引节点中。而目录项只需要包含文件名、索引节点指针。

我们访问文件时,可以通过索引结点指针找到索引结点的位置,再从中找到文件的物理地址。

4.2.1 软链接(符号链接)

window下软链接与快捷方式的区别

  • 快捷方式自己就是一个文件,它的大小比较固定,里面包含了指向真实文件的信息;
  • 查看软链接大小的时候,软链接的大小会被计算为指向的文件或目录的大小
4.2.2 硬链接
  1. macos里面,当 A、B两个项目同时安装 axios-a,然后在 A 里面的 node_modules 里面对 axios-a 进行修改,那么B里面的内容也将发生修改,因为它们的最终都指向的是相同的路径。也就是说(A/node_modules/.pnpm/axios-a@1.0.3/node_modules/axios-aB/node_modules/.pnpm/axios-a@1.0.3/node_modules/axios-astore里面的内容是同一个)

Anode_modules/axios-a/index.js 里面添加了1,打开B里面相同的文件,会发现同样发生修改。

  1. windows里面,当修改A里面的axios-a的内容,并不会导致B里面的axios-a内容的修改,具体的表现参看 4.4
4.2.3 区分软、硬链接

软链接通常都会有一个箭头符号

windows 电脑里面可以通过vscode进行查看

image.png

4.3 store

  1. 根据 pnpm store path得到的路径结果删除里面的内容,删除pnpm-lock.yaml,然后执行 pnpm add axios-a,可以看到我们的 store 里面有3个文件。

image.png

而我们的 aixos-a 里面共有2个文件

image.png

分别在vscode里面打开 store 里面的内容可以发现

8fxxxx --> package.json
61xxxx --> index.json

另外我们打开 4bxxxx,可以看到它的内容如下

image.png

其中 integrity 就是这个文件通过hash算法得到的hash值。当我们使用 pnpm add pkg 安装包的时候,会对文件执行 hash 算法,并与integrity进行比较,如果不相同,说明发生了改变,pnpm 将重新拉取最新的npm包,从而保证每次 pnpm install链接的包都是最新的。

4.4 修改node_modules的表现

场景:当我们要调试第三方包的时候,通常会对第三方进行修改

4.4.1 windows

当我们修改 node_modules/axios-a 里面的内容以后,如果要获取最新的包的内容,仅仅是 pnpm uninstall axios-a 是不够的,因为 .pnpm/axios-a还在,此时如果直接执行 pnpm add axios-a,会直接从 .pnpm/axios-a 里面进行链接,此时 axios-a对应的内容还是修改后的内容。

直接使用 yarn install 也是无效的,
image.png

所以有效的解决方案是直接删除 node_modules ,然后重新执行 pnpm install

5. 其他注意事项

5.1 对项目中某些插件的影响

  1. 公司项目使用的引入排序的插件是 @trivago/prettier-plugin-sort-imports,当使用 pnpm 的时候该插件会失效,解决方法
// .prettierrc.js
module.exports = {
  // ...
  plugins: ['./node_modules/@trivago/prettier-plugin-sort-imports'],
}