从pnpm到npm的核心管理机制(一)

简介: 我们应该了解完整的npm包管理生态,然后才可以得知为什么pnpm打击了哪些痛点,以及如何应用到我们的实际项目中。

背景


今年在github前端领域star上升速度比较主要有以下几个:

  • Svelte, 一个将MVVM架构放到构建应用程序的编译阶段实现的框架,让你开发更少的代码实现更多的功能
  • Typescript, 几乎所有的前端框架不约而同的支持了Typescript,一个JavaScript的超集,支持变量类型声明
  • pnpm,一个现代化的npm包管理工具,采用link方式去全局管理包,这是本文介绍的重点。


因此,选一个和我们项目中开发相关的作为一个知识扩展点,应该就是pnpm了。


其实从事前端这么多年,自从npm包出现以后,前端工程化就一路顺畅,但是随之也带来很多问题,包括但不限于:

  • 版本管理难
  • 安装速度慢
  • 多个项目安装依赖占用空间大
  • ...


那么其实我们应该了解完整的npm包管理生态,然后才可以得知为什么pnpm打击了哪些痛点,以及如何应用到我们的实际项目中。

npm


之前已经在《从npm版本依赖到Monorepo大仓项目》已经介绍过npm是什么,定义如下:

npm,Node Package Manager的缩写,也就是“Node的包管理器”。 npm(“Node 包管理器”)是 JavaScript 运行时 Node.js 的默认程序包管理器。

也了解它的版本是如何管理的,但是我们还缺乏对它的核心功能需要了解,具体了解以下几个方面:

  • npm install xxx, npm如何将远程的npm包下载到我们的本地node_modules
  • npm ls, npm如何如何维护npm包在本地的关系的
  • npm run xxx,npm如何执行我们的脚本命令


首先,我们需要知道npm由三部分组成:

  • npm包管理网站
  • npm-cli,命令行工具
  • npm包注册中心,npm registry


接下来就是注册账号,以及包的发布,其中版本管理这块,是遵循APR版本规范(major.minor.patch版本模型规范),具体可看这里npm版本管理

npm包管理


目前npm包下载方式分为两种,一种是全局,一种是当前项目,其都会放在node_modules目录下。下面通过以下几方面去讲述npm是如何管理npm包的:

  • 循环依赖
  • 版本冲突
  • 同个版本包收敛


我们以这个下面这个包管理树为例:

foo
+-- blerg@1.2.5
+-- bar@1.2.3
|   +-- blerg@1.x (latest=1.3.7)
|   +-- baz@2.x
|   |   `-- quux@3.x
|   |       `-- bar@1.2.3 (cycle)
|   `-- asdf@*
`-- baz@1.2.3
    `-- quux@3.x
        `-- bar

我们从上面可以看出:

  • blerg 有两个版本分别是 1.2.51.x ,这是同个次版本的包放在不同地方
  • bar 版本是1.2.3,但是依赖 blergbaz,但是依赖baz包的版本是2.x,这是版本冲突
  • baz 版本是1.2.3,,但是所依赖的包中有个依赖bar 这就是循环依赖


那么npm是如何解决这类问题的呢?npm会将上述包管理树优化成如下所示:

foo
+-- node_modules
    +-- blerg (1.2.5) <---[A]
    +-- bar (1.2.3) <---[B]
    |   +-- node_modules
    |       +-- baz (2.0.2) <---[C]
    +-- asdf (2.3.4)
    +-- baz (1.2.3) <---[D]
    +-- quux (3.2.0) <---[E]

那么npm的遵循策略是什么呢?其实还是APR版本规范(major.minor.patch版本模型规范)。

  • 第一,会将依赖树打平,从树结构变成一级结构,解决了循环依赖问题
  • 第二,会将主项目依赖的npm包提到一级,然后对于相同次版本minor中的包统一版本,这就是同个版本包收敛,节省空间
  • 第三,接着将同一个包,但是不同版本的包一次放到对应依赖包内,形成二级依赖(node_modules),这就解决了版本冲突


require加载顺序

  • 加载核心模块,如:fs、path等
  • 加上对应文件后缀,优先级为:test.js > test.json > test.node
  • 搜索路径,如果有指定路径则按照路径去找,如:require('./test') 则在当前目录寻找
  • 如果没有指定路径,则从当前目录下往上去找 node_modules文件夹,然后从文件夹里去遍历寻找对应模块名,如果找不到则到上一层node_modules去找,直到最顶层目录
  • 首次会加载比较慢,后面node.js 会将缓存相关信息到内存避免二次查询

npm install执行过程


b0451d135d71a97950799f2f793a50a.png

npm run执行过程


npm run serve为例,执行过程如下:

bd1be189a1b45a303bb20526d2d7a9d.png

痛点问题


npm的管理包已经能解决大部分开发问题了,那么为什么还会有出现yarnpnpm等新型管理工具,主要npm包存在目前难以解决的痛点问题:

  • yarn新增yarn.lock,可以解决npm包版本变动问题,目前已被npm引入特性,生成package-lock.json
  • npm包多个项目依赖包一致,但每个项目都需要重新安装,不仅耗时,且占用磁盘空间,这是pnpm解决了的问题
  • 包经常创建太深的依赖树,这导致 Windows 上的目录路径过长,这是pnpm解决的问题
  • 平铺的结构不是 node_modules 的唯一实现方式,pnpm作者描述:目前node_modules大部分为了解决重复依赖包的问题,把npm依赖树进行打平,这样子会产生一些问题:
  • 项目可以访问一些不依赖的npm包
  • 打平依赖树的算法非常复杂,导致安装时更加慢
  • node_modules目录结构十分复杂


这些痛点问题都不是npm和yarn能解决的,因此才会pnpm的出现,接下来我们再了解一下pnpm。

pnpm


官网是这么定义的:

快速的,节省磁盘空间的npm包管理工具

同时还支持以下这些特性:

  • 快速: 比其他包管理器快 2 倍
  • 高效:node_modules 中的文件为复制或链接自特定的内容寻址存储库
  • 支持 monorepos:  内置支持单仓多包
  • 严格:默认创建了一个非平铺的 node_modules,因此代码无法访问任意包


这里安装教程就忽略了,推荐的用法是直接使用npm安装全局命令:

npm install -g pnpm

其他安装方式可以参考官网教程:pnpm 安装

实现原理


从作者博客或者github源码,我们可以尝试去推测实现pnpm几个特性所需要关键原理。

更快速的install


pnpm为了加快npm install,从以下两个方面进行优化:

  • 创建一个.pnpm store,除了第一次安装需要按照npm包,后续再次安装,将会创建一个符号链接到npm包
  • 不打平npm 依赖树,节省复杂依赖树打平时间


可以参考官网的架构图,去更好了解pnpm针对node_moduels的管理,如下:

37f45a2ef6807cc8ca1fbab15303fd8.png

这里有几个设计点需要弄清楚:

1.pnpm将所有依赖树的目录,软链接到.pnpm store中对应的npm包

2.node_moduels将不会有package.json依赖(devDependenciesdependencies)外的npm包

3.node_moduels的目录结构,和npm依赖树保持一致


通过上述几个点,pnpm将拥有比yarnnpm更快的install速度。


额外知识点

1.Linux文件链接分为软链接和硬链接,两者有什么区别?

  • Linux中的符号链接,就是我们平时说的软连接,可以针对文件、目录创建,但是源文件删除后链接不可用,命令:ln -s xxx xxx
  • Linux中的硬链接,只能针对文件,但是文件删除仍可使用,命令:ln xxx xxx

2.Windows如何解决符号链接问题?


pnpm采用junctions,具体如下:

  • Windows的文件系统是基于NTFS架构,本身就支持hard link 、junctions和symbolic links
  • Hard Link和Linux中硬链接没什么区别,同样只支持文件类型创建链接,采用函数: CreateHardLinkADeleteFileA
  • Junctions是符号链接,也叫软链接,支持文件、目录创建链接,实现:junction 命令行

更安全的install


我们从上文可以得在Node.js中,require()是如何查询对应npm包,是从node_modules一层层向上查找获取的。


由于pnpm使用非扁平化的node_modules管理依赖树,因此对于非package.json依赖的npm包,是不会放在项目的node_modules中,从而使得开发者无法获取无依赖的npm包。具体可以参考下图:

0ed36443107afd7d502407e485468dc.png



目录
相关文章
|
6月前
|
存储 缓存 JavaScript
npm link 与 pnpm link 的用法以及不同之处
npm link 与 pnpm link 的用法以及不同之处
442 0
|
5月前
|
存储 缓存 资源调度
你真的知道 NPM、Yarn 与 PNPM 这三个前端包管理器之间的区别吗?
【6月更文挑战第9天】NPM、Yarn和PNPM是主流前端包管理器,各有特色。NPM生态庞大,易用但速度慢;Yarn速度快,依赖管理稳定;PNPM性能优异,节省磁盘空间。Yarn和PNPM在速度和确定性上胜出,NPM因广泛使用和丰富资源领先。开发者可根据项目需求和喜好选择,三者共同推动前端开发进步。
150 8
|
5月前
|
存储 缓存 资源调度
npm、yarn与pnpm详解
npm、yarn与pnpm详解
136 0
|
6月前
|
存储 资源调度 JavaScript
pnpm、npm、yarn是什么?怎么选择?
pnpm、npm、yarn是什么?怎么选择?
229 2
|
6月前
|
缓存 资源调度 JavaScript
npm、pnpm和yarn【简单了解】
npm、pnpm和yarn【简单了解】
51 2
|
6月前
|
存储 缓存 资源调度
三巨头对决:深入了解pnpm、yarn与npm
三巨头对决:深入了解pnpm、yarn与npm
882 0
|
6月前
|
资源调度
pnpm : 无法加载文件 C:\Users\Administrator\AppData\Roaming\npm\pnpm.ps1,因为在此系统上禁止运行脚本。
pnpm : 无法加载文件 C:\Users\Administrator\AppData\Roaming\npm\pnpm.ps1,因为在此系统上禁止运行脚本。
|
1月前
|
缓存 资源调度 JavaScript
npx与npm的差异解析,以及包管理器yarn与Node版本管理工具nvm的使用方法详解
npx与npm的差异解析,以及包管理器yarn与Node版本管理工具nvm的使用方法详解
32 0
2071 verbose node v16.6.0 2072 verbose npm v7.19.1或者 no such file or directory, lstat ‘D:\wor
该博客文章提供了解决在使用npm版本7.19.1时出现的"no such file or directory"错误的具体方法,建议通过降级npm到6.14.8版本来解决问题,并确认了该方法可以成功安装node_modules。
2071 verbose node v16.6.0 2072 verbose npm v7.19.1或者 no such file or directory, lstat ‘D:\wor
|
3月前
|
缓存 JavaScript 前端开发
成功解决:npm 版本不支持node.js。【 npm v9.1.2 does not support Node.js v16.6.0.】
这篇文章介绍了如何解决npm版本与Node.js版本不兼容的问题,提供了查看当前npm和Node.js版本的步骤,以及如何根据Node.js版本选择合适的npm版本并进行升级的详细指导。
成功解决:npm 版本不支持node.js。【 npm v9.1.2 does not support Node.js v16.6.0.】
下一篇
无影云桌面