Skip to content
On this page

包管理工具

pnpm(高性能的npm)

npm和yarn的问题

  • npm@3之前:嵌套安装依赖
    • windows上目录路径过长
    • 同个package被多次复制生成多份文件
  • npm@3+和yarn:将依赖扁平化
    • 幻影依赖:指在没有package.json的情况下引用其他包的依赖
    • 依赖结构的不稳定性,不同安装顺序可能不同
    • npm包分身,不同包下的同个依赖会被安装多次
      • 占用空间
      • 非单例,可以出现多个库实例

pnpm如何解决

前置知识

  • inode:每个文件都有唯一的inode,包含了元信息,可以使用stat查看。
  • hard link:一个相互的指针,创建hardlink 指向源文件的 inode,系统并不会重新分配inode。硬链接不管有多少个,都指向的是同一个 inode 节点,这意味着当你修改源文件或者链接文件的时候,都会做同步的修改。每新建一个 hardlink 会把节点连接数增加,只要节点的链接数非零,文件就一直存在,不管你删除的是源文件还是 hradlink。只要有一个存在,文件就存在
    • .pnpm 中的每个文件都是来自内容可寻址存储的硬链接
  • soft link:软链接可以理解为是一个单向指针,是一个独立的文件且拥有独立的 inode,永远指向源文件。类比于 Windows 系统的快捷方式。删除源文件,软链接就会失效。

注意几个地方

  • 根目录下的node_modules
    • 只有直接依赖的包,且通过 软链接 链接到.pnpm 目录中
  • .pnpm:虚拟存储目录,所有直接和间接的依赖都链接到这里,通过 <package-name>@<version> 来实现相同模块不同版本之间隔离和复用。
  • Store:在全局通过Store来存储所有的 node_modules 依赖,并且在 .pnpm中存储项目的hard links
    • 在使用 pnpm 对项目安装依赖的时候,如果某个依赖在 sotre 目录中存在了话,那么就会直接从 store 目录里面去 hard-link,避免了二次安装带来的时间消耗,如果依赖在 store 目录里面不存在的话,就会去下载一次。

**假如全局的包变得非常大怎么办?

**使用方法为pnpm store prune ,它提供了一种用于删除一些不被全局项目所引用到的 packages 的功能

例如有个包 axios@1.0.0 被一个项目所引用了,但是某次修改使得项目里这个包被更新到了 1.0.1 ,那么 store 里面的 1.0.0 的 axios 就就成了个不被引用的包,执行 pnpm store prune 就可以在 store 里面删掉它。

可视化分析

我们项目中有一个依赖 bar@1.0.0。bar@1.0.0也有一个依赖 foo@1.0.0。

  • node_modules 下面有 bar@1.0.0 和 .pnpm 目录,没有 foo@1.0.0
  • bar@1.0.0 通过软链接指向 .pnpm/bar@1.0.0/node_modules/bar@1.0.0.pnpm/bar@1.0.0/node_modules/bar@1.0.0 又通过硬链接指向 Store
  • bar@1.0.0 依赖的foo@1.0.0 会安装在跟自己的同一级,这里的设计,我理解是根据 node 的 require 机制,bar 中 require('foo') 的时候,就会先找到 foo@1.0.0,而不会往上寻找,这样就避免依赖包版本不一致的问题。.pnpm/bar@1.0.0/node_modules/foo@1.0.0。并通过软链接指向 .pnpm 下一级的 foo@1.0.0
  • .pnpm/foo@1.0.0 一样通过硬链接指向 Store

npm

  • node默认的,以JavaScript开发的,基于NodeJs的命令行工具

安装流程

  • 运行npm install,会先去查找配置信息
    • 检查项目是否存在.npmrc文件,没有的话查找全局的,还是没有的话,使用内置的配置文件
  • 获取完配置后,根据package文件构建依赖树
    • 如果package-lock.json存在且与package.json中的一致,使用lock中的信息
    • 反之使用package.json中的信息
  • 有了依赖树,根据依赖树下载完整的依赖资源
    • 下载前会检查是否有缓存,存在则直接解压到node_modules
    • 不存在则会到远程仓库下载,下载完通过完整性校验后将其添加到缓存并且解压到node_modules中(默认不会安装到全局,可以手动加-g
  • 生成或更新package-lock.json文件,记录下依赖包的版本信息,可以保证在以后的任何时候,安装的都是同样的依赖,避免由于版本不一致导致bug的出现

不同版本生成依赖树的区别

npm 2.X

  • 简单的循环遍历方式
  • 缺点
    • 存在大量冗余的依赖
    • node_modules大小指数级上升
    • 由于层级过深,导致路径过长报错

npm 3.X

  • 更改为扁平化的层级关系
  • 缺点
    • 生成的依赖树会因为依赖的顺序不同而不同
    • 不同时机安装的依赖版本不同

npm 5.X

  • 新增package-lock.json文件,存储确定的依赖信息

npm缓存机制

npm config get cache可以获取缓存的位置

  • content-v2存放缓存包的具体内容
  • index-v5存储依赖包的索引
  • 根据index-v5中的索引去content-v2中查找具体的源文件

资料显示:npm 在安装依赖的过程中,会根据 lock文件中具体的包信息,用 pacote:range-manifest:{url}:{integrity} 规则生成出唯一的 key,然后用这个 key 通过 SHA256 加密算法得到一个 hash。这个 hash 对应的就是 _cache/index-v5 中的文件路径,前 4 位 hash 用来区分路径,剩下的几位就是具体的文件名。文件的内容就是该缓存的具体信息了。

其中 _shasum 是 SHA-1 加密返回的 40 位十六进制小写密文。这一步中 _shasum 又充当了源文件包索引的作用,我们可以用这个 _shasum_cache/content-v2 中找一下对应的文件。

我们试着找一下

执行npm install lodash,在生成的lock文件中,可以看到:

loadsh包的integrityurl分别是

sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==

https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz

通过https://emn178.github.io/online-tools/sha256.html我们可以计算出对应的key值,通过规则,我们确实找到了缓存的文件。

image

我们找到对应的文件,拿到其中的_shasum值为679591c564c3bffaae8454cf0b3df370c3d6911c,通过规则访问对应的地址,可以得到是一个gzip文件,解压后则是lodash的源码

如何保证依赖包的完整性

资源完整性校验是npm安装机制的安全保证

在下载依赖之前,该资源包就有一个 hash 值,我们可以在 npm 提供的网站上看到所有这个包的版本信息:registry.npmjs.com/lodash 找一下我们下载的 4.17.21 这个版本。

json
"4.17.21":{
  ...
  "dist":{
    "integrity":"sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
    "shasum":"679591c564c3bffaae8454cf0b3df370c3d6911c",
    "tarball":"https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
    "fileCount":1054,
    "unpackedSize":1412415,
  }
}

可以看到一个熟悉的字段 shasum 。我们只需要在下载完成之后在本地用相同的算法再次计算得出一个 shasum,对比两个 shasum:如果相同,就代表下载的依赖是完整的;反之,则说明下载过程中出现了问题,会再次重新下载。

开发中的最佳实践

  • npm使用5.7+版本(package-lock.json生成逻辑在5.6+逐渐稳定)
  • package.jsonpackage-lock.json 提交到远端仓库,同时 ignore掉 node_modules文件
  • 更改依赖后,将更新的 package.json 文件和 package-lock.json 文件一起提交到远端仓库,项目成员拉取新代码并更新依赖
  • 如果 package-lock.json 文件冲突,应该先手动解决 package.json 的冲突,然后执行 npm install --package-lock-only,让 npm 自动帮你解冲突。可以参考 官方推荐做法

MIT Licensed | Copyright © 2021 - 2022