在前端开发中,我们每天都会与各种npm包打交道,选择一个合适的包管理工具能显著提升开发体验和构建效率。近年来,pnpm作为一款新兴的包管理工具逐渐受到越来越多开发者青睐,它究竟有何魅力能让众多开源项目纷纷转投其怀抱?本文将带你深入了解pnpm的原理与优势。
# 一、为什么需要更好的包管理工具
# 1.1 npm与yarn的发展历程
在早期npm 1、npm 2时代,项目的node_modules会呈现出嵌套结构:
node_modules
└─ foo
├─ index.js
├─ package.json
└─ node_modules
└─ bar
├─ index.js
└─ package.json
这种嵌套依赖树设计存在三个严重问题:
问题一:依赖层级太深
这会导致文件的路径过长的问题,尤其在Windows系统下文件路径默认最多支持256个字符。
问题二:包重复安装
比如foo和zoo都依赖于bar,那么bar就会在两者的node_modules中被安装两次,导致项目体积暴涨。
问题三:模块实例不能共享
比如React有一些内部变量,在两个不同包引入的React不是同一个模块实例,因此无法共享内部变量,导致一些不可预知的bug。
后来yarn横空出世解决了这些问题,npm也在3.0版本中沿用了yarn的解决方案,这个解决方案就是扁平化依赖。
# 1.2 扁平化依赖带来的新问题
所谓的扁平化依赖就是将所有依赖铺平,放到同一级目录下:
node_modules
├─ foo
│ ├─ index.js
│ └─ package.json
└─ bar
├─ index.js
└─ package.json
虽然之前的问题得到了解决,但这种扁平化的处理方式自身也存在许多问题:
问题一:幽灵依赖
所谓的幽灵依赖是指我们明明没有在package.json的dependencies里声明某个依赖,但在代码里却可以import进来。因为项目依赖被铺平了,那么依赖的依赖自然也是可以被引入到项目中。
幽灵依赖带来的弊端很明显:我们显式依赖了A,A又依赖了B,这时候我们在项目中直接使用B是可以的,但如果某一天A不再依赖于B,那么我们项目中使用B的地方就会报错。
问题二:依赖结构不确定性
举例来说,如果项目同时依赖foo和bar,而它们都依赖不同版本的lodash,那么最终安装哪个版本是不确定的,完全取决于package.json中的声明顺序。
这就是为什么会产生依赖结构的不确定问题,也是lock文件诞生的原因,无论是package-lock.json还是yarn.lock,都是为了保证install之后都产生确定的node_modules结构。
问题三:非法访问依赖
由于扁平化结构,如果A依赖B,B依赖C,那么A当中是可以直接使用C的,但A并没有声明C这个依赖,这就是非法访问。
在monorepo项目中,如果A依赖X,B依赖X,还有一个C,它不依赖X,但它代码里面用到了X。由于依赖提升的存在,npm/yarn会把X放到根目录的node_modules中,这样C在本地是能够跑起来的。但试想一下,一旦C单独发包出去,用户单独安装C,那么就找不到X了,执行到引用X的代码时就直接报错了。
正是这些问题促使了pnpm的诞生。
# 二、pnpm是什么
pnpm(Performant NPM)是一个快速的、节省磁盘空间的包管理工具,同时它还对monorepo有良好的支持。
# 安装pnpm
npm i pnpm -g
它的用处与npm和yarn并没有什么本质区别,甚至连用法都十分相似,但却在性能和安全性方面有显著优势。
# 三、pnpm核心优势
# 3.1 安装速度快
根据官方文档的benchmark测试数据,pnpm在大多数场景下的安装速度都要优于npm和yarn。
可以看到,pnpm在绝多大数场景下,包安装的速度都是明显优于npm/yarn,速度会比npm/yarn快2-3倍。
对yarn比较熟悉的同学可能会说,yarn不是有<