Npm / Yarn / Pnpm 前端包管理工具对比

您所在的位置:网站首页 hse管理工具有几种 Npm / Yarn / Pnpm 前端包管理工具对比

Npm / Yarn / Pnpm 前端包管理工具对比

#Npm / Yarn / Pnpm 前端包管理工具对比| 来源: 网络整理| 查看: 265

本次分享不会包含使用方式,如感兴趣可以自行查看

简介

前端的包管理工具相信大家一定不会陌生,因为每天都需要跟他打交道,新项目或者刚拉下来的前端项目都需要去 install 依赖进行包的依赖安装,大家最熟悉的应该就是 npm 了,或者国内的 npm 镜像包 cnpm ,大家熟称为淘宝镜像

但是现在,npm 已经是前端家喻户晓的存在了,为什么还会出现诸如 cnpm Yarn pnpm Yarn2 等等...今天就让我带大家一起一探究竟,为什么已经出现如此之久的 npm 还会有重复造轮子的包管理呢?

包管理工具的功能 处理和编写元数据 批量安装或更新所有依赖项 添加、更新和删除依赖项 运行脚本 发布软件包 进行安全审查 简史

第一个发布的软件包管理器是 npm ,早在 2010 年就已经存在了。它确立了如今包管理的核心,在前端包管理工具相当于是一种标准了。

如今 npm 已经存在 12 年了,为什么还有其他替代品?

node_modules 不同的依赖解析算法,嵌套 VS 扁平化 不同的安全问题 不同的锁文件格式,有性能影响 在磁盘上存储包的不同方式,对磁盘空间有效率影响 image 对多包(单一代码库)项目的不同支持 不同程度的可配置性和灵活性 主流包管理器 npm Yarn pnpm Yarn Berry (还未发布) npm

image

npm 是包管理器的祖先。许多人错误地认为 npm 是 “Node 包管理器” 的首字母缩写,但事实并非如此。尽管如此,它与 Node.js 运行时捆绑在一起。

在 npm 之前,项目依赖都是手动下载和管理的。

npm 引入了一些概念:

package.json 文件 元数据字段(例如,devDependencies) node_modules 中存储依赖项 自定义脚本 公共和私有包注册

在 node_modules 中存储依赖项、自定义脚本、公共和私有包注册等概念都是 npm 引入的

Yarn (v1)

image

Yarn 是 Facebook 宣布与谷歌和其他一些公司开发新的软件包管理器,主要解决 npm 当时存在的一致性、安全性和性能问题,他们命名为 Yarn

Yarn 的架构设计建立在 npm 许多概念和流程之上,Yarn 在最初的发布中对包管理器产生了重大影响。Yarn 在安装依赖的过程中采用了并行安装,这是 npm 当时的一大痛点

Yarn 还发明了自己的许多概念,例如:

原生 monorpo 支持 缓存感知安装 离线缓存 锁文件

目前 Yarn 的热度在包管理器的热度上也是数一数二了。

特点 速度快 Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载 同时利用并行下载以最大化资源利用率,因此安装速度更快 安全 在执行代码之前,Yarn 会通过算法校验每个安装包的完整性 可靠 使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作 创新性 离线模式 如果你以前安装过某个包,再次安装时可以在没有任何互联网连接的情况下进行。 确定性 不管安装顺序如何,相同的依赖关系将在每台机器上以相同的方式安装 网络性能 Yarn 有效地对请求进行排队处理,避免发起的请求如瀑布般倾泻,以便最大限度地利用网络资源 相同的软件包 从 npm 安装软件包并保持相同的包管理流程 网络弹性 重试机制确保单个请求失败并不会导致整个安装失败 扁平模式 将依赖包的不同版本归结为单个版本,以避免创建多个副本 pnpm

image

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 层实现的,使用符号链接创建一个嵌套的依赖关系结构,其中文件夹中的每个包都是到存储的硬链接。

image

这是为什么 pnpm 会在快速和磁盘效率上有大幅提升的原因。

它是如何工作的?

官网介绍

如果依赖于依赖项的不同版本,则只有不同的文件才会添加到存储区。例如,如果它有100个文件,而一个新版本只在其中一个文件中有更改,pnpm update 将只向存储中添加一个新文件,而不是为了这个单一的更改而克隆整个依赖。 所有的文件都保存在磁盘上的一个地方。安装包时,它们的文件将从该位置硬链接,不消耗额外的磁盘空间。这允许您在项目之间共享相同版本的依赖项。

由于这种依赖关系的链接,它也比它的替代品快 2 倍。通过使用这项技术和一些真正高性能的缓存解决方案,您可以在眨眼之间安装包

Yarn Berry

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 berry 比较特殊,需要通过当前目录进行安装,而不是作为一个全局管理,类似于只安装当前文件内

升级 Yarn 2 或以上版本 // 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

image

Yarn Berry

image

Pnpm

image

可以发现依赖包的大小为 npm > Yarn Berry > Pnpm

Yarn Berry主要是将依赖下载成 zip 形式存储,但是 Node 无法解析 zip 格式的依赖包,所以使用了 .pnp.js 来维护映射关系,我们将 Yarn Berry 生成的所有依赖可以直接上传到 git 上,其他成员拉下代码后,即可直接运行,实现 Zero Install

对比 玄学 依赖管理 安全 速度 1. 玄学

不知道大家在使用过 npm 或者 Yarn 的时候有没有一种感受,就是在输入 Yarn install 或者 Yarn 的时候,要比 npm 更加舒适?

image

由于 n p m 三个字母都在键盘的右侧区域,所以正常打字的话可能需要一只手去输入。所以这就造成了 npm 的复杂度是 O(n),而 Yarn 的复杂度为 O(log n),虽然多了一个字母,但是分别在左右手区域各两个,所以在输入时更加顺畅,你自己在输入 np 的时候,你就已经可以把 yarn 输入完成了。

所以 pnpm 在输入上更略逊一筹,比 npm 还要难以输入...

2. 依赖管理 依赖结构

安装依赖时的原理:

将依赖包的版本区间解析为某个具体的版本号 下载对应版本依赖的 tar 包到本地离线镜像 将依赖从离线镜像解压到本地缓存 将依赖从缓存拷贝到当前目录的 node_modules 目录 npm 在 npm v1 npm v2 版本中,依赖包的管理是树结构嵌套组成的 node_modules └─ foo ├─ index.js ├─ package.json └─ node_modules └─ bar ├─ index.js └─ package.json

如果出现大量重复的包,将重复安装多个,会导致 node_modules 非常巨大,形成嵌套地狱。与我们之前在 JS 内写回调地狱类似

v3 版本之后使用扁平化管理 Yarn 默认使用扁平化依赖管理

安装一个 React,发现在 node_modules 目录内有其他包文件

. └── node_modules ├── js-tokens ├── loose-envify ├── object-assign └── react ├── cjs ├── node_modules └── umd

将 React 需要的依赖都给打平到 node_modules 目录内,这个方案解决了嵌套地狱的问题,根据 node require 机制会不停的往上级 node_modules 去寻找,找到了就不会安装,解决了大量包重复安装的问题。

虽然解决了,但是扁平化的处理方式还存在一些问题。

模块可以访问他们并不依赖的包 平展依赖树的算法非常复杂 一些软件包在一个项目中被复制 node_modules 模块/文件夹 pnpm

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/[email protected]/node_modules/react

我们发现,除了一个我们不认识的 .pnpm 文件夹,只有一个 react 目录。 那么所有的次级依赖去哪里了呢?就在 .pnpm 的文件夹里面,我们打开后可以看到所有的依赖(包括依赖的依赖)都在 .pnpm 文件夹内,所以 react 是唯一一个你的应用必须拥有访问权限的包。

外面的 可以看到 react 是一个符号链接指向了它的真实位置

react 包的真实位置在 /node_modules/.pnpm/[email protected]/node_modules/react

所有你安装的依赖都存在 .pnpm/@/node_modules/ ,官方称它为虚拟存储目录

看一下 react 真实的位置内容

.pnpm/[email protected]/node_modules/react/node_modules

里面没有 node_modules 目录,那么 react 的依赖去哪里了?

其实 react 的所有依赖都被软链到了 node_modules/.pnpm/ 中的对应目录了,这样将所有依赖放置同一级别可以避免循环的软链

对比一下 Npm / Yarn / Pnpm 工作机制

画了一张略微有点乱的图

image

3. 安全

npm / yarn 的扁平依赖结构,有一个非常严重的问题就是可以非法访问未声明的包

举个🌰

我们使用 Antd 包,可以直接引用 Antd 内部实现包,例如 rc-table

image

image

那么 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/[email protected]/node_modules/object-assign/index.js') console.log(objectAssign) [Function: assign] 缓存

npm / Yarn:把 tgz 解包成 tar 作为全局缓存,再次安装依赖时解压到 node_modules。

Yarn Berry:把所有的文件下载到当前项目中,压缩成 zip 的形式存储

pnpm:把 tgz 解压为文件,以 hash 方式全局缓存, 同个包的不同版本的同个文件也能共享,再次安装时直接硬链接过去。

4. 速度 npm 35s

image

Yarn 4s

image

pnpm 2s

image

Yarn Berry 14s

image

测试下磁盘效率

用了一个多包的仓库测试的~

左侧是 pnpm ,右侧是 yarn / npm

image

pnpm 官网特性对比表 功能pnpmYarnnpm工作空间支持(monorepo)✔️✔️✔️隔离的 node_modules✔️ - 默认✔️✔️提升的 node_modules✔️✔️✔️ - 默认Plug'n'Play✔️✔️ - 默认✔️零安装❌✔️❌修补依赖项❌✔️❌管理 Node.js 版本✔️❌❌有锁文件✔️ - pnpm-lock.yaml✔️ - Yarn.lock✔️ - package-lock.json支持覆盖✔️✔️ - 通过 resolutions✔️内容可寻址存储✔️❌❌动态包执行✔️ - 通过 pnpm dlx✔️ - 通过 Yarn dlx✔️ - 通过 npx 开源库使用的包管理工具

举几个开源库的包管理使用情况

npmYarnYarn BerrypnpmsvelteReactJestVue 3ExpressNext.jsBabelBrowserlistApollp ServerWebpack-cliRedux ToolkitSvelteKit

image

总结

个人在 Yarn 刚出没多久的时候就已经在使用了,算是 Yarn 的常年老用户粉了。从 0.x 的版本开始,因为之前用 npm 不是安装依赖太慢,就是安装中途出错,或者设置淘宝镜像去使用。所以我干脆就换成 Yarn 了。一直使用至今的 1.22.x 版本

所以我在看到有人用 npm 的时候就忍不住一直在推荐 Yarn 作为包管理工具,每次接手或者新开发的项目也是。。。

在对 npm / Yarn / Pnpm / Yarn Berry 做了简单的对比之后相信大家自己心里会有一定的判断

希望以后包管理工具可以有一个标准规范吧,这样的话我们也不用去纠结使用哪些工具,对比他们的内部实现以及对业务上的支持等

参考链接 PNPm JavaScript package managers compared: npm, Yarn, or pnpm? Yarn 的 Plug'n'Play 特性 Why should we use pnpm? NPM vs Yarn vs PNPM: Which Node.js Package Manager Should You Use? 


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3