今天研究了一下 pnpm 的机制,发现它确实很强大,甚至可以说对 yarnnpm 形成了降维打击 。

我们从包管理工具的发展历史,一起看下到底好在哪里?

npm2

在 npm 3.0 版本之前,项目的 node_modules 会呈现出嵌套结构,也就是说,我安装的依赖、依赖的依赖、依赖的依赖的依赖...,都是递归嵌套的

node_modules
├─ express
│ ├─ index.js
│ ├─ package.json
│ └─ node_modules
│ ├─ accepts
│ │ ├─ index.js
│ │ ├─ package.json
│ │ └─ node_modules
│ │ ├─ mime-types
| | | └─ node_modules
| | | └─ mime-db
| │ └─ negotiator
│ ├─ array-flatten
│ ├─ ...
│ └─ ...
└─ A
├─ index.js
├─ package.json
└─ node_modules
└─ accepts
├─ index.js
├─ package.json
└─ node_modules
├─ mime-types
| └─ node_modules
| └─ mime-db
└─ negotiator

设计缺陷

这种嵌套依赖树的设计确实存在几个严重的问题

  1. 路径过长问题: 由于包的嵌套结构 , node_modules 的目录结构可能会变得非常深,甚至可能会超出系统路径长度上限 ,毕竟 windows 系统的文件路径默认最多支持 256 个字符
  2. 磁盘空间浪费: 多个包之间难免会有公共的依赖,公共依赖会被多次安装在不同的包目录下,导致磁盘空间被大量浪费 。比如上面 express 和 A 都依赖了 accepts,它就被安装了两次
  3. 安装速度慢:由于依赖包之间的嵌套结构,npm 在安装包时需要多次处理和下载相同的包,导致安装速度变慢,尤其是在依赖关系复杂的项目中

当时 npm 还没解决这些问题, 社区便推出了新的解决方案 ,就是 yarn。 它引入了一种新的依赖管理方式——扁平化依赖。

看到 yarn 的成功,npm 在 3.0 版本中也引入了类似的扁平化依赖结构

yarn

yarn 的主要改进之一就是通过扁平化依赖结构来解决嵌套依赖树的问题,具体来说

铺平,yarn 尽量将所有依赖包安装在项目的顶层 node_modules 目录下,而不是嵌套在各自的 node_modules 目录中。

这样一来,减少了目录的深度,避免了路径过长的问题 ,也尽可能避免了依赖被多次重复安装的问题

我们可以在 yarn-example 看到整个目录,全部铺平在了顶层 node_modules 目录下,展开下面的包大部分是没有二层 node_modules

然而,有些依赖包还是会在自己的目录下有一个 node_modules 文件夹,出现嵌套的情况,例如 yarn-example 下的http-errors 依赖包就有自己的 node_modules,原因是:

当一个项目的多个依赖包需要同一个库的不同版本时,yarn 只能将一个版本的库提升到顶层 node_modules 目录中。 对于需要这个库其他版本的依赖,yarn 仍然需要在这些依赖包的目录下创建一个嵌套的 node_modules 来存放不同版本的包

比如,包 A 依赖于 lodash@4.0.0,而包 B 依赖于 lodash@3.0.0。由于这两个版本的 lodash 不能合并,yarn 会将 lodash@4.0.0 提升到顶层 node_modules,而 lodash@3.0.0 则被嵌套在包 B 的 node_modules 目录下。

幽灵依赖

虽然 yarn 和 npm 都采用了扁平化的方案来解决依赖嵌套的问题,但这种方案本身也有一些缺陷,其中幽灵依赖是一个主要问题。

幽灵依赖,也就是你明明没有在 package.json 文件中声明的依赖项,但在项目代码里却可以 require 进来

这个也很容易理解,因为依赖的依赖被扁平化安装在顶层 node_modules 中,所以我们能访问到依赖的依赖

但是这样是有隐患的,因为没有显式依赖,未来某个时候这些包可能会因为某些原因消失(例如新版本库不再引用这个包了,然后我们更新了库),就会引发代码运行错误

浪费磁盘空间

而且还有一个问题,就是上面提到的依赖包有多个版本的时候,只会提升一个,那其余版本的包不还是复制了很多次么,依然有浪费磁盘空间的问题

那社区有没有解决这俩问题的思路呢? pnpm 就是其中最成功的一个

pnpm

pnpm 通过全局存储和符号链接机制从根源上解决了依赖重复安装和路径长度问题,同时也避免了扁平化依赖结构带来的幽灵依赖问题

pnpm 的优势概括来说就是“快、准、狠”:

  • 快:安装速度快
  • 准:安装过的依赖会准确复用缓存,甚至包版本升级带来的变化都只 diff,绝不浪费一点空间
  • 狠:直接废掉了幽灵依赖

执行 npm add express,我们可以在 pnpm-example 看到整个目录,由于只安装了 express,那 node_modules 下就只有 express

那么所有的(次级)依赖去哪了呢? binggo,在node_modules/.pnpm/目录下,.pnpm/ 以平铺的形式储存着所有的包

三层寻址

  1. 所有 npm 包都安装在全局目录 ~/.pnpm-store/v3/files 下,同一版本的包仅存储一份内容,甚至不同版本的包也仅存储 diff 内容。
  2. 顶层 node_modules 下有 .pnpm 目录以打平结构管理每个版本包的源码内容,以硬链接方式指向 pnpm-store 中的文件地址。
  3. 每个项目 node_modules 下安装的包以软链接方式将内容指向 node_modules/.pnpm 中的包。

所以每个包的寻找都要经过三层结构:node_modules/package-a > 软链接 node_modules/.pnpm/package-a@1.0.0/node_modules/package-a > 硬链接 ~/.pnpm-store/v3/files/00/xxxxxx

这就是 pnpm 的实现原理。官方给了一张原理图,可以搭配食用

前面说过,npm 包都被安装在全局 pnpm store ,默认情况下,会创建多个存储(每个驱动器(盘符)一个),并在项目所在盘符的根目录

所以,同一个盘符下的不同项目,都可以共用同一个全局 pnpm store,绝绝子啊,大大节省了磁盘空间,提高了安装速度

软硬链接

也就是说,所有的依赖都是从全局 store 硬连接到了 node_modules/.pnpm 下,然后之间通过软链接来相互依赖。

那么,这里的软连接、硬链接到底是什么东西?

硬链接是指向磁盘上原始文件所在的同一位置 (直接指向相同的数据块)

软连接可以理解为新建一个文件,它包含一个指向另一个文件或目录的路径 (指向目标路径)

.npmrc

shamefully-hoist,默认 false

  • false:node_modules下只能看到直接依赖的套件,次级依赖在node_modules/.pnpm 目录下;无法访问其他子包局部安装的依赖项,例如,vue-dome2 安装的 lodash,vue-dome1 是访问不到的
  • true:將所有套件都拉升到 node_modules 目錄下,能访问到其他子包局部安装的依赖项,例如,vue-dome2 安装的 lodash,vue-dome1 是能访问到的
// .npmrc

# pnpm 配置
shamefully-hoist=false

总结

npm2 的嵌套结构: 每个依赖项都会有自己的 node_modules 目录,导致了依赖被重复安装,严重浪费了磁盘空间;在依赖层级比较深的项目中,甚至会超出 windows 系统的文件路径长度

npm3+ 和 Yarn 的扁平化策略: 尽量将所有依赖包安装在项目的顶层 node_modules 目录下,解决了 npm2 嵌套依赖的问题。但是该方案有一个重大缺陷就是“幽灵依赖”;而且依赖包有多个版本时,只会提升一个,那其余版本依然会被重复安装,还是有浪费磁盘空间的问题

pnpm全局存储和符号链接机制: 结合软硬链和三层寻址,解决了依赖被重复安装的问题,更加变态的是,同一盘符下的不同项目都可以共用一个全局 pnpm store。节省了磁盘空间,并且根本不存在“幽灵依赖”,安装速度还贼快

参考文档

weekly/前沿技术/253.精读《pnpm》

pnpm 是凭什么对 npm 和 yarn 降维打击的)

平铺的结构不是 node_modules 的唯一实现方式 | pnpm中文网

pnpm 是如何颠覆 npm 和 yarn 的?的更多相关文章

  1. 一文看懂npm、yarn、pnpm之间的区别

    文作者对比了当前主流的包管理工具npm.yarn.pnpm之间的区别,并提出了合适的使用建议,以下为译文: NPM npm是Node.js能够如此成功的主要原因之一.npm团队做了很多的工作,以确保n ...

  2. [转] 一文看懂npm、yarn、pnpm之间的区别

    [From] http://geek.csdn.net/news/detail/197339 原文:Understanding differences between npm, yarn and pn ...

  3. 主流包管理工具npm、yarn、cnpm、pnpm之间的区别与联系——原理篇

    接触 node 之后,一直使用npm包管理工具, cnpm 一开始会用一些,但是并没有觉得比 npm 快得多,使用 cnpm 的时候还经常安装不成功,只能再用 npm 安装一遍,渐渐的就弃用了 cnp ...

  4. Npm vs Yarn 之备忘大全

    有则笑话,如此讲到:"老丈人爱吃核桃,昨天买了二斤陪妻子送去,老丈人年轻时练过武,用手一拍核桃就碎了,笑着对我说:你还用锤子,你看我用手就成.我嘴一抽,来了句:人和动物最大的区别就是人会使用 ...

  5. Node入门教程(7)第五章:node 模块化(下) npm与yarn详解

    Node的包管理器 JavaScript缺少包结构的定义,而CommonJS定义了一系列的规范.而NPM的出现则是为了在CommonJS规范的基础上,实现解决包的安装卸载,依赖管理,版本管理等问题. ...

  6. npm与yarn命令对比

    Yarn是由Facebook.Google.Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具 Yarn 是为了弥补 npm 的一些缺陷而出现的(比如,npm install时候会 ...

  7. npm和yarn的使用对比

    NPM YARN 说明 npm init yarn init 初始化某个项目 npm install/link yarn install/link 默认的安装依赖操作 npm install taco ...

  8. npm和yarn的区别

    npm和yarn的区别,我们该如何选择? 周一入职,同事JJ让我熟悉一下基于React的新项目.按照以往,我的步骤都是: git clone xxx npm install npm run dev 这 ...

  9. nodejs中npm以及yarn常用指令

    1.npm下载相关 1.npm install/i vue //下载vue的包 2.npm i vue --save-dev / -D //下载vue的包,并添加到开发依赖中 3.npm i //下载 ...

  10. 安装使用yarn,使用国内镜像加速npm和yarn

    安装yarn https://yarnpkg.com/lang/zh-hans/docs/install/ 使用国内镜像加速npm和yarn 1. npm config set registry=ht ...

随机推荐

  1. ELK快速部署(踩坑记录、常见报错解决)及常用架构讲解

    ELK = Elasticserach + Logstash + kibana(包含但不仅限于) 简介: Elasticsearch:分布式搜索和分析引擎,具有高可伸缩.高可靠和易管理等特点.基于 A ...

  2. 【Docker学习教程系列】7-如何将本地的Docker镜像发布到阿里云

    在上一篇中,我们使用docker commit 命令,创建了一个带有vim的Ubuntu镜像.那么怎么将这个镜像分享出去呢?本文就来讲解如何将本地的docker镜像发布到阿里云上. 本文主要内容: 1 ...

  3. WebShell流量特征检测_哥斯拉篇

    80后用菜刀,90后用蚁剑,95后用冰蝎和哥斯拉,以phpshell连接为例,本文主要是对这四款经典的webshell管理工具进行流量分析和检测. 什么是一句话木马? 1.定义 顾名思义就是执行恶意指 ...

  4. 前端解决Long类型精度丢失的问题

    问题 数据库数据: 前端得到的数据: 出现了Long类型的数据出现精度丢失问题! 原因 JS中Long最大值:9007199254740992 JAVA中Long最大值:922337203685477 ...

  5. ASP.NET Core – TagHelper

    前言 以前写的 Asp.net core 学习笔记之 Tag Helper, 这篇是整理版. 参考 Docs – Author Tag Helpers in ASP.NET Core Creating ...

  6. HTML – script async defer

    参考 Youtube – #3 JavaScript Loading Strategies (async and defer) | JavaScript Full Tutorial no async ...

  7. 暑假集训SCP提高模拟10

    我(看着百度百科):我已经知道这场谁组的题了 CTH: 谁 我:你想想,能在模拟赛里塞四道数学题还玩邦的,还能有谁 CTH: 我不知道 我:我不知道 CTH: 我知道了 我:我知道了 我:我是 Bob ...

  8. 【赵渝强老师】什么是Spark SQL?

    一.Spark SQL简介 Spark SQL是Spark用来处理结构化数据的一个模块,它提供了一个编程抽象叫做DataFrame并且作为分布式SQL查询引擎的作用. 为什么要学习Spark SQL? ...

  9. 活动预告 | 中国数据库联盟(ACDU)中国行第三站定档成都,邀您探讨数据库前沿技术

    数据库技术一直是信息时代中不可或缺的核心组成部分,随着信息量的爆炸式增长和数据的多样化,其重要性愈发凸显.作为中国数据库联盟(ACDU)的品牌活动之一,[ACDU 中国行]在线下汇集数据库领域的行业知 ...

  10. 05-react的类组件和函数组件 -- 状态 state

    // 函数组件是无状态的 既没有数据的 类似 vue 组件中的 data 数据 // 类组件是有状态的组件 是有数据的 是双向绑定的数据 是数据驱动视图的 负责UI的视图更新 (单个组件的私有数据组件 ...