本次分享不会包含使用方式,如感兴趣可以自行查看
前端的包管理工具相信大家一定不会陌生,因为每天都需要跟他打交道,新项目或者刚拉下来的前端项目都需要去 install
依赖进行包的依赖安装,大家最熟悉的应该就是 npm
了,或者国内的 npm
镜像包 cnpm
,大家熟称为淘宝镜像
但是现在,npm
已经是前端家喻户晓的存在了,为什么还会出现诸如 cnpm
Yarn
pnpm
Yarn2
等等...今天就让我带大家一起一探究竟,为什么已经出现如此之久的 npm
还会有重复造轮子的包管理呢?
第一个发布的软件包管理器是 npm
,早在 2010 年就已经存在了。它确立了如今包管理的核心,在前端包管理工具相当于是一种标准了。
如今 npm
已经存在 12 年了,为什么还有其他替代品?
npm 是包管理器的祖先。许多人错误地认为 npm 是 “Node 包管理器” 的首字母缩写,但事实并非如此。尽管如此,它与 Node.js 运行时捆绑在一起。
在 npm 之前,项目依赖都是手动下载和管理的。
npm 引入了一些概念:
在 node_modules 中存储依赖项、自定义脚本、公共和私有包注册等概念都是 npm 引入的
Yarn 是 Facebook 宣布与谷歌和其他一些公司开发新的软件包管理器,主要解决 npm 当时存在的一致性、安全性和性能问题,他们命名为 Yarn
Yarn 的架构设计建立在 npm 许多概念和流程之上,Yarn 在最初的发布中对包管理器产生了重大影响。Yarn 在安装依赖的过程中采用了并行安装,这是 npm 当时的一大痛点
Yarn 还发明了自己的许多概念,例如:
monorpo
支持目前 Yarn 的热度在包管理器的热度上也是数一数二了。
Yarn
缓存了每个下载过的包,所以再次使用时无需重复下载Yarn
会通过算法校验每个安装包的完整性pnpm 是一个比较新颖的包管理工具。它和 Yarn 一样,是为了解决某些 npm 痛点的。可以说是 npm 的替换,如果你现在的项目是 npm 项目,那么可以直接使用 Pnpm
Pnpm 的出现是为了解决 Yarn 的问题,因为 Yarn 不解决例如磁盘占用的问题以及内部的发展不公开等原因,所以就自己去开发了一个,目前在使用体验上要比 Yarn 好一些而且解决了一些 Yarn 目前存在的问题以及痛点,感兴趣的同学可以看看原文。Why should we use pnpm?
虽然 Yarn 的速度优于 npm,但是它使用了相同的依赖解析方法
现在的前端项目越来越庞大,复杂。很多时候有成百上千的依赖包,每次安装都需要一定的时间,并且大量浪费磁盘空间。
pnpm 引入了一种替代的依赖解析策略:内容寻址存储。
这个方式导致你的 node_modules 文件夹里面的依赖包都将存储在 ~/.pnpm-store/
下。每个依赖包的版本在该文件夹中只存储一次,构成唯一来源,这样的话将会节省相当多的磁盘空间。
这是通过 node_modules
层实现的,使用符号链接创建一个嵌套的依赖关系结构,其中文件夹中的每个包都是到存储的硬链接。
这是为什么 pnpm
会在快速和磁盘效率上有大幅提升的原因。
官网介绍
由于这种依赖关系的链接,它也比它的替代品快 2 倍。通过使用这项技术和一些真正高性能的缓存解决方案,您可以在眨眼之间安装包
Yarn 2
也称为 Yarn Berry
,2020 年 1 月发布,据称是对 Yarn
的重大升级。它本质上是一个新的包管理器,新的代码基础和新原则,所以称为 Yarn Berry
。
Yarn Berry 太激进了,所以我们只简单讨论一些吧,感兴趣的同学可以自己去看看,目前最新版本已经到 3 了
Yarn Berry
的主要创新是 PnP(Plug'n'Play,即插即用 ),这种方法是修复 node_modules 的策略而产生的。相当于抛弃了 node_modules
原生 node 的查找依赖方式是向上级目录层层递归遍历 node_modules 文件夹,虽然,现有的包管理版本都已经做到了依赖提升,让依赖项尽量扁平化,但当碰到包依赖版本不匹配的时候,仍然会存在嵌套目录。 而 PnP,它记录了依赖的准群硬盘位置,可以在查找依赖时减少硬盘读写,同时,可以做到所有依赖项完全扁平化。
本质上,就是将你的依赖项通过下载并解析成 zip 的形式放到你的 .yarn/cache
目录下,通过提交源码将当前所有的 zip 文件上传,然后当其他团队成员在 down
代码的时候直接可以运行项目而不需要特意去安装。
因为 Yarn berry
比较特殊,需要通过当前目录进行安装,而不是作为一个全局管理,类似于只安装当前文件内
// yarn 版本在 1.22+
yarn set version berry
// 安装 react
yarn add react
依赖结构如下:
.
├── .pnp.cjs
├── .yarn
│ ├── .DS_Store
│ ├── cache
│ │ ├── .gitignore
│ │ ├── js-tokens-npm-4.0.0-0ac852e9e2-8a95213a5a.zip
│ │ ├── loose-envify-npm-1.4.0-6307b72ccf-6517e24e0c.zip
│ │ ├── object-assign-npm-4.1.1-1004ad6dec-fcc6e4ea8c.zip
│ │ └── react-npm-17.0.2-99ba37d931-b254cc17ce.zip
│ ├── install-state.gz
│ └── releases
│ └── yarn-berry.js
├── .yarnrc.yml
├── package.json
└── yarn.lock
依赖大小
使用 create-react-app
默认依赖进行对比
可以发现依赖包的大小为 npm > Yarn Berry > Pnpm
Yarn Berry
主要是将依赖下载成 zip 形式存储,但是 Node 无法解析 zip 格式的依赖包,所以使用了 .pnp.js 来维护映射关系,我们将 Yarn Berry
生成的所有依赖可以直接上传到 git 上,其他成员拉下代码后,即可直接运行,实现 Zero Install
不知道大家在使用过 npm
或者 Yarn
的时候有没有一种感受,就是在输入 Yarn install
或者 Yarn
的时候,要比 npm
更加舒适?
由于 n p m
三个字母都在键盘的右侧区域,所以正常打字的话可能需要一只手去输入。所以这就造成了 npm
的复杂度是 O(n),而 Yarn
的复杂度为 O(log n),虽然多了一个字母,但是分别在左右手区域各两个,所以在输入时更加顺畅,你自己在输入 np
的时候,你就已经可以把 yarn
输入完成了。
所以 pnpm 在输入上更略逊一筹,比 npm 还要难以输入...
安装依赖时的原理:
npm v1
npm v2
版本中,依赖包的管理是树结构嵌套组成的node_modules
└─ foo
├─ index.js
├─ package.json
└─ node_modules
└─ bar
├─ index.js
└─ package.json
如果出现大量重复的包,将重复安装多个,会导致 node_modules
非常巨大,形成嵌套地狱。与我们之前在 JS 内写回调地狱
类似
v3
版本之后使用扁平化管理安装一个 React
,发现在 node_modules 目录内有其他包文件
.
└── node_modules
├── js-tokens
├── loose-envify
├── object-assign
└── react
├── cjs
├── node_modules
└── umd
将 React
需要的依赖都给打平到 node_modules
目录内,这个方案解决了嵌套地狱的问题,根据 node require
机制会不停的往上级 node_modules
去寻找,找到了就不会安装,解决了大量包重复安装的问题。
虽然解决了,但是扁平化的处理方式还存在一些问题。
pnpm 会创建"奇怪"的 node_modules 结构
pnpm 解决的不是平铺目录所带来的问题,而是解决 npm v3
版本之前的树结构的依赖问题
我们先创建两个目录进行比较,先建立一个 npm 的包管理项目,然后在建立一个 pnpm 包管理项目
npm init
npm install react
然后看一下 npm/node_modules
里面的内容
.
├── js-tokens
├── loose-envify
├── object-assign
└── react
如上所述,建立了平铺的结构,其他我们不认识的依赖都是 React 本身的依赖,被打平在这儿
我们在继续在 pnpm 里进行操作
pnpm init
pnpm install
├── .pnpm
└── react -> .pnpm/react@17.0.2/node_modules/react
我们发现,除了一个我们不认识的 .pnpm
文件夹,只有一个 react
目录。 那么所有的次级依赖去哪里了呢?就在 .pnpm
的文件夹里面,我们打开后可以看到所有的依赖(包括依赖的依赖)都在 .pnpm
文件夹内,所以 react
是唯一一个你的应用必须拥有访问权限的包。
外面的 可以看到 react
是一个符号链接指向了它的真实位置
react
包的真实位置在 /node_modules/.pnpm/react@17.0.2/node_modules/react
所有你安装的依赖都存在 .pnpm/<name>@<version>/node_modules/<name>
,官方称它为虚拟存储目录
看一下 react
真实的位置内容
.pnpm/react@17.0.2/node_modules/react/node_modules
里面没有 node_modules
目录,那么 react
的依赖去哪里了?
其实 react
的所有依赖都被软链到了 node_modules/.pnpm/
中的对应目录了,这样将所有依赖放置同一级别可以避免循环的软链
画了一张略微有点乱的图
npm / yarn
的扁平依赖结构,有一个非常严重的问题就是可以非法访问未声明的包
举个🌰
我们使用 Antd
包,可以直接引用 Antd
内部实现包,例如 rc-table
那么 pnpm 像上文介绍一样,将依赖通过 link 的形式避免了非法访问依赖的问题,如果没在 package.json
声明的话,是无法访问的。
在举个🌰
我们在 yarn
的包管理工具下,引入一个 react
使用的包 object-assign
。然后在 index.js 下输入以下代码
const objectAssign = require('object-assign')
console.log(objectAssign)
[Function: assign]
继续在 pnpm
下使用上述代码以及对应流程
在控制台输出 Cannot find module 'object-assign'
如果你要硬刚写路径,也不是不可以访问。。。
const objectAssign = require('./node_modules/.pnpm/object-assign@4.1.1/node_modules/object-assign/index.js')
console.log(objectAssign)
[Function: assign]
npm / Yarn:把 tgz 解包成 tar 作为全局缓存,再次安装依赖时解压到 node_modules。
Yarn Berry:把所有的文件下载到当前项目中,压缩成 zip 的形式存储
pnpm:把 tgz 解压为文件,以 hash 方式全局缓存, 同个包的不同版本的同个文件也能共享,再次安装时直接硬链接过去。
用了一个多包的仓库测试的~
左侧是 pnpm ,右侧是 yarn / npm
功能 | pnpm | Yarn | npm |
---|---|---|---|
工作空间支持(monorepo) | ✔️ | ✔️ | ✔️ |
隔离的 node_modules | ✔️ - 默认 | ✔️ | ✔️ |
提升的 node_modules | ✔️ | ✔️ | ✔️ - 默认 |
Plug'n'Play | ✔️ | ✔️ - 默认 | ✔️ |
零安装 | ❌ | ✔️ | ❌ |
修补依赖项 | ❌ | ✔️ | ❌ |
管理 Node.js 版本 | ✔️ | ❌ | ❌ |
有锁文件 | ✔️ - pnpm-lock.yaml | ✔️ - Yarn.lock | ✔️ - package-lock.json |
支持覆盖 | ✔️ | ✔️ - 通过 resolutions | ✔️ |
内容可寻址存储 | ✔️ | ❌ | ❌ |
动态包执行 | ✔️ - 通过 pnpm dlx | ✔️ - 通过 Yarn dlx | ✔️ - 通过 npx |
举几个开源库的包管理使用情况
npm | Yarn | Yarn Berry | pnpm |
---|---|---|---|
svelte | React | Jest | Vue 3 |
Express | Next.js | Babel | Browserlist |
Apollp Server | Webpack-cli | Redux Toolkit | SvelteKit |
个人在 Yarn 刚出没多久的时候就已经在使用了,算是 Yarn 的常年老用户粉了。从 0.x 的版本开始,因为之前用 npm 不是安装依赖太慢,就是安装中途出错,或者设置淘宝镜像去使用。所以我干脆就换成 Yarn 了。一直使用至今的 1.22.x 版本
所以我在看到有人用 npm 的时候就忍不住一直在推荐 Yarn 作为包管理工具,每次接手或者新开发的项目也是。。。
在对 npm / Yarn / Pnpm / Yarn Berry 做了简单的对比之后相信大家自己心里会有一定的判断
希望以后包管理工具可以有一个标准规范吧,这样的话我们也不用去纠结使用哪些工具,对比他们的内部实现以及对业务上的支持等
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有