pnpm介绍

1,932 阅读6分钟

前言

年前为公司升级了新的monorepo项目架构,近来不用加班,在家得闲。便想再搭个monorepo项目,随便记录一下。
用到以下pnpm + vite3 + vue3 + ESLint + Stylelint + TypeScriptmonorepe实战记录,此篇为其中一个的简单介绍。

pnpm是什么

pnpm是一个JavsScript包管理工具,是现有工具(npm、yarn)的衍生产物,拥有和npm、yarn类似的功能,以及和类似的使用方式。和众多产物一样,它的出现是为了解决现有工具的某些问题。而pnpm越来越受开发者喜爱,通过这篇文章你也将会了解到pnpm的优势。

官网介绍:快速的,节省磁盘空间的包管理工具。这里所有说的快速和节约磁盘空间是相对于目前传统包管理工具(yarn,npm)而言,pnpm更加快速,更节省磁盘。

符号链接(软链接)和硬链接

pnpm使用了软连接和硬链接两种方式管理依赖包,在此之前需要先了解下什么是软连接和硬链接。这里我按自己所理解的介绍,也可自行百度了解哈。

  • 软链接
    软链接和快捷方式有点相似,是一个指向其他位置的文件[夹],软链接可正常读写文件(读写的就是源文件的内容)。和快捷方式一样,当源文件删除,软链接也无法访问了。但它和快捷方式又有所不同,快捷方式会跳转到实际位置,但软链接不会。

  • 硬链接
    硬链接支持文件的链接,而不支持文件夹的链接。硬链接和源文件几乎没有什么区别,和源文件共享一个inode号(文件在文件系统上的唯一标识)。他们共享同样的文件内容,如果一个源文件(或硬链接)改变内容,那么硬链接(或源文件)内容也同样改变。删除源文件后,硬链接可依旧正常使用。

包管理

为什么有了yarnnpm,还会推出pnpm呢。那就不得不聊聊yarn和npm所存在的问题了。

yarn和npm包管理方式

yarnnpm(3.x之后)采用的平铺的方式,将所有依赖平铺安装的node_modules下。如图 image.png package.json只有几个依赖,但在node_modules下却安装了一堆的包,这是因为yarnnpm将依赖(包括依赖的依赖)平铺到node_modules下。
每一个项目都需要将对应的依赖安装到node_modules下,哪怕不同项目有相同的依赖,也都需要下载安装到各自的项目node_modules下。这既消耗时间,又浪费磁盘空间

平铺方式还存在一个幽灵依赖的问题,比如某库A依赖库B,那么A、B库都会被平铺安装到node_modules下。如果项目中引入了库B,因为库B也被安装到node_modules下,所以项目可直接访问。万一哪天项目不再需要库A,而将库A删除(或有人重拉项目安装完依赖后),却发现项目因找不到依赖B而跑不起来。这就是所谓的幽灵依赖:项目中引入没有被定义在其package.json文件中 的包

npm2.x版本采用的是嵌套的方式,将一级依赖安装到node_modules下,二级依赖安装到对应的一级依赖的node_modules下,三级依赖又被安装的二级依赖的node_modules下,以此类推...。这样嵌套的安装方式存在严重的磁盘浪费,下载更加费时,而且window的文件路径还存在长度限制。于是后面版本就采用平铺的方式。

pnpm的包管理方式

了解npm和yarn目前存在的一些问题,可以先停下来想想假如是你会怎样优化呢?

是不是可以将依赖安装到一个公用的存储库中,当项目在安装依赖时,如果存储库中有该项目某些依赖就通过某种方式直接链接到当前项目的node_modules下,那么这些依赖就不用再从网上下载安装了。pnpm使用的就是这种方法。

pnpm有个公共包存储.pnpm store(位置在当前盘的/.pnpm-store/v3/files/下),硬链接着node_modules/.npm下对应的依赖包。
当项目使用pnpm安装依赖包时,只会将package.json中注册的依赖包到node_modules下,并且pnpm的node_modules是基础符号链接(软连接)的结构,里面的依赖包都会软链到node_modules/.npm下,包括多级依赖也都通过软链的方式平铺在.pnpm下。
如图,node_modules下根据package.json安装的依赖包都有软链符号,它们是都被软链到.npm下了。依赖安装包的实际位置在node_modules/.npm/包名@版名号/node_modules/包名image.png 而且安装时会校验存储库中是否有对应的包

  • 如果有直接从.pnpm store创建硬链接到 node_modules/.npm/对应包/node_modules/包名 下。
  • 没有则会下载,再创建硬链接到.npm store

如官方提供的原理包所示。 image.png

pnpm利用存储库,减少了重复包的下载,如果使用了相同包的不同版本也只会将差异部分添加到仓库,大大减少了包安装时间以及磁盘空间的消耗。并在只会将package.json里的依赖安装到node_modules下,避免了幽灵依赖的问题。

工作空间(Workspace)

pnpm 内置了对单一存储库(也称为多包存储库、多项目存储库或单体存储库)的支持, 可以创建一个 workspace 以将多个项目合并到一个仓库中,方便我们管理monorepo项目。
项目根目录新建pnpm-workspace.yaml文件,用于定义工作空间包含或排除了哪些目录。

packages:
  // 包含
  - 'packages/*'
  // ! 排除工作区外
  - '!**/test/*'

Workspace 协议 (workspace:)

假设工作区内有两个项目(common 和 layout),common有两个依赖layout@1.0.0, baba@1.0.0

+ packages
| + common   packages: { name: "common", "version": "1.0.0", dependencies: { "layout": "^1.0.0", "baba": "^1.0.0" } }
| + layout   packages: { name: "layout", "version": "1.0.0", }

工作空间下的包可以相互引用,而且默认情况下会优先尝试从工作空间下查找,没有查找到再从npm registry安装(当然从npm registry安装前会先判断存储库是否有)。
如上面假设,当common安装依赖时会先在工作区查找(查到包layout@1.0.0),pnpm会从工作区将layout@1.0.0链接到common。依赖包baba@1.0.0在工作区没找到则从npm registry安装。

pnpm 支持 workspace 协议 workspace: 。 例如当项目common的依赖这样设置"layout": "workspace:1.0.0",那么pnpm只会从本地工作区查找,如工作区没有则会安装失败。

当根目录下的.npmrc文件(需手动创建)设置link-workspace-packages选项被设置为false时,仅当使用 workspace: 协议声明依赖,pnpm 才会从此 workspace 链接所需的包。

.npmrc有很多配置,可自行前往查阅。

workspace 协议还可以通过有以下方式引用 workspace包。以 common 引用 layout 为例:

  • 别名:"layout": "workspace:*"
  • 相对路径:"layout": "workspace:../layout"

结语

pnpm的介绍到此就结束了,当然还很多东西没介绍,推荐到pnpm官网翻阅。如有不对的地方还请谅解,欢迎指出~