跳到主要内容

认识

2023年02月06日
柏拉文
越努力,越幸运

一、认识


二、特点


三、工作


3.1 解析依赖树

NPM 读取项目的 package.json 文件,解析所有的依赖项及其版本要求。确定需要安装的所有依赖项,包括直接依赖和间接依赖(子依赖)。

3.2 下载包

NPMnpm registry 下载所有需要的包,包括直接依赖和子依赖。将包下载到项目的 node_modules 目录中。

3.3 创建依赖树

NPM 将所有下载的包安装到 node_modules 目录中。直接依赖项存放在 node_modules 目录的根目录下,子依赖项存放在它们各自的父依赖项的 node_modules 目录下。

3.4 生成 package-lock.json 文件

创建或更新 package-lock.json 文件,记录所有安装的依赖项及其精确版本和依赖关系。

3.5 最终的 node_modules 扁平目录结构

假设项目依赖于 expresslodash,并且 express 依赖于 accepts

我们理解的情况应该是这样的

project
└── node_modules
├── accepts
├── express
│ ├── node_modules
│ │ └── accepts
│ └── ...(其他依赖)
├── lodash
└── ...(其他依赖)
  • 每个包的依赖项存放在它们各自的 node_modules 目录中,形成一个嵌套的依赖树结构。

  • 相同版本的依赖项可能会在不同的目录中重复出现,增加了磁盘空间的使用。

但是,NPM 实际上会尽可能将 accepts 安装在 node_modules 的根目录中,而不是嵌套在 expressnode_modules 目录下。, 如下所示:

project
└── node_modules
├── accepts
├── express
├── lodash
└── ...(其他依赖)

NPM 扁平结构: 在 NPM 的扁平结构中,node_modules 目录尽可能地将依赖项安装在根目录中,以减少嵌套层级。这种扁平化的安装方式旨在避免过深的目录结构和长路径问题。NPM 扁平结构 可以避免过深的目录嵌套,减少了路径长度的问题。安装方式也很简单。但是,可能导致版本冲突。多个包依赖不同版本的同一依赖项时,NPM 可能会将它们扁平化到同一目录下,造成版本冲突。而且重复安装依赖项。相同的依赖项可能在多个项目中重复下载和安装,浪费磁盘空间。

四、问题


4.1 npm vs cnpm vs yarn vs pnpm

npm: 在 NPM 的扁平结构中,node_modules 目录尽可能地将依赖项安装在根目录中,以减少嵌套层级。这种扁平化的安装方式旨在避免过深的目录结构和长路径问题。

cnpm: cnpm 不生成 lock 文件,也不会识别项目中的lock文件

yarn

pnpm: PNPM 使用了一种非扁平结构,它在 node_modules 目录下创建一个 .pnpm 子目录,实际的包内容存储在这个子目录中,然后通过 符号链接(symlink 将依赖项链接到项目的 node_modules 目录中。这种结构确保了依赖项的隔离,避免了版本冲突。

4.2 简述 NPM 的发展历程以及各个阶段的问题?

v1/v2:

  • 存在问题: 嵌套依赖、重复依赖, node_modules 体积过大, 嵌套过深

  • 问题描述: 最开始其实没有注重 npm 包的管理,只是简单的嵌套依赖,这种方式层级依赖结构清晰,但是随着 npm 包的增多,项目的迭代扩展,重复包越下载越多,造成了空间浪费,导致前端本地项目 node_modules 动辄上百 M。在业务开发中,安装几个项目,项目体积好几G,对使用者们极其不友好。入下图所示,依赖包 CAB 中都被引用了, 被重复下载了两次,其实是两个完全相同的东西。

  • 问题图解:

    Preview

v3:

  • 优化方案: 由于 v1/v2 版本存在 嵌套依赖、重复依赖, node_modules 体积过大, 嵌套过深 的问题, 所以 npm 团队也意识到这个问题,通过扁平化的方式,将子依赖安装到了主依赖所在项目中,以减少依赖嵌套太深,和重复下载安装的问题。如下图所示,A 的依赖项C 被提升到了顶层,如果后续有安装包,也依赖C,会去上一级的node_modules查找,如果有相同版本的包,则不会再去重复下载,直接从上一层拿到需要的依赖包C

  • 优化图解:

Preview
  • 优化效果: 扁平化方式解决了相同包重复安装的问题, 也一定程度上解决了依赖层级太深的问题。为什么说是一定程度上呢? 因为如上图所示,B 依赖的C v2.0.0,并没有提升,依然是嵌套依赖。因为在两个依赖包 C 的版本号不一致,只能保证一个在顶层,上图所示C v1.0.0 被提升了,v2.0.0 没有被提升,后续 v2.0.0 还是会被重复下载,所以当出现多重依赖时,依然会出现重复安装的问题。而且这个提升的顺序,也不是根据使用量优先提升,而是根据先来先服务原则,先安装的先提升。这会导致不确定性问题,随着项目迭代,npm i 之后得到的 node_modules 目录结构,有可能不一样。

  • 存在问题: 幽灵依赖

  • 问题描述: 如优化方案、优化图解、优化效果所述, 通过扁平化的方式, 将重复的子依赖依赖的某一个版本提升至了主依赖所在项目, 提升到了顶层。如上所示, 我们把C,提升到了顶层。 即使项目 package.json,没有声明过C,但是也可以在项目中引用到C,这就是幽灵依赖问题。

v5:

  • 优化方案: 对于v3存在的不确定性提升子依赖的问题, npm v5 借鉴 yarn 的思想,新增了 package-lock.json。该文件里面记录了 package.json 依赖的模块,以及模块的子依赖。并且给每个依赖标明了版本、获取地址和验证模块完整性哈希值。

  • 优化效果: 通过 package-lock.json,保障了依赖包安装的确定性与兼容性,使得每次安装都会出现相同的结果。这个就解决了不确定性的问题

  • package-lock.json:

  • 文件字段:

    • name:项目的名称;

    • version:项目的版本;

    • lockfileVersion:lock文件的版本;

    • requires:使用requires来跟踪模块的依赖关系;

    • dependencies:项目的依赖

      • version: 表示实际安装的版本;

      • resolved: 用来记录下载的地址,registry仓库中的位置;

      • requires: 记录当前模块的依赖;

      • integrity: 用来从缓存中获取索引,再通过索引去获取压缩包文件

4.3 为什么自己的 node_modules 没有C,也能在上层访问到 C 呢?

答: require 寻找第三方包,会每层级依次去寻找 node_modules,所以即便本层级没有node_moudles,上层有,也能找到

4.4 NPM node_modules 扁平结构和 PNPM node_modules 的非扁平结构式如何理解的?

NPM 扁平结构: 在 NPM 的扁平结构中,node_modules 目录尽可能地将依赖项安装在根目录中,以减少嵌套层级。这种扁平化的安装方式旨在避免过深的目录结构和长路径问题。NPM 扁平结构 可以避免过深的目录嵌套,减少了路径长度的问题。安装方式也很简单。但是,可能导致版本冲突。多个包依赖不同版本的同一依赖项时,NPM 可能会将它们扁平化到同一目录下,造成版本冲突。而且重复安装依赖项。相同的依赖项可能在多个项目中重复下载和安装,浪费磁盘空间。

PNPM 非扁平结构: PNPM 使用了一种非扁平结构,它在 node_modules 目录下创建一个 .pnpm 子目录,实际的包内容存储在这个子目录中,然后通过 符号链接(symlink 将依赖项链接到项目的 node_modules 目录中。这种结构确保了依赖项的隔离,避免了版本冲突。PNPM 非扁平结构 通过 符号链接.pnpm 目录隔离依赖项,确保不同项目之间的依赖项互不干扰,避免版本冲突, 全局存储和符号链接机制避免了重复安装相同版本的包, 节省了磁盘空间。全局存储和并行安装提高了安装速度。但是,PNPM 需要处理符号链接,可能会对一些工具和脚本造成兼容性问题。

4.5 如果要更新某个依赖,直接修改 package.json 里的依赖版本,然后删除node_modules重新安装所有依赖可以吗?

通常情况下,这样操作是可以的

一、package.json 里面修改某个依赖版本

二、删除 node_modules

三、重新安装依赖包 yarn

某个情况下的操作

首先: 直接修改 package.json 里面的依赖版本,然后删除,这样操作的话是不会修改 package-lock.json 里面的版本的,再一次安装的时候有可能之前的版本和之后的版本都安装了

一、npm uni 依赖 删除指定依赖 二、npm i 依赖 重现安装指定全新依赖

参考资料


从npm发展历程看pnpm的高效