npm 安装机制

165 阅读3分钟

1、npm 模块安装机制

  • 发出npm install命令
  • 查询node_modules目录之中是否已经存在指定模块
    • 若存在,不再重新安装
    • 若不存在
      • npm 向registry 查询模块压缩包的网址
      • 下载压缩包,存放在根目录下的.npm目录里
      • 解压压缩包到当前项目到node_modules目录

2、npm实现原理

输入npm install命令并敲下回车后,会经历如下几个阶段(npm5.5.1):

1、执行工程自身preinstall

当前npm工程如果定义了preinstall钩子此时会被执行

2、确定首层依赖模块

首先需要做到是确定工程中的首层依赖,也就是dependencies和devDependencies属性中直接指定的模块(假设此时没有添加npm install参数)。
工程本身是整棵依赖树的根节点,每个首层依赖模块都是根节点下面的一棵子树,npm会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。

3、获取模块

获取模块是一个递归的过程,分为一下几步:

  • 获取模块信息。在下载一个模块之前,首先要确定其版本,这是因为package.json中往往是semantic version (sermver,语义化版本)。此时如果版本描述文件(npm-shrinkwrap.json或package-lock.json)中有该模块信息直接拿就可以,如果没有则从仓库获取。如package.json中某个包的版本是^1.1.0,npm就会去仓库中获取符合1.x.x形式的最新版本。
  • 获取模块实体。上一步会获取到模块的压缩包地址(resolved字段),npm会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。
  • 查找该模块依赖,如果有依赖则回到第一不,如果没有则停止。

4、模块扁平化(dedupe)

上一步获取到的是一棵完整的毅=依赖树,其中可能包含大量复用模块。比如A模块依赖于lodash,B模块同样依赖于lodash。在npm3以前会严格按照依赖树进行安装,因此会造成模块冗余。

从npm3开始默认加入一个dedupe的过程。它会遍历所有节点,逐个将模块放到根节点下面,也就是node_modules的第一层。当发现有重复模块时,则将其丢弃。

这里需要对重复模块进行一个定义,它指的是模块名相同且semver兼容。每个semver都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在dedupe过程中被去掉。

比如node-modules下foo模块依赖lodash@^1.0.0,bar模块依赖lodash@^1.1.0,则^1.1.0为兼容版本。

而当foo依赖lodash@^2.0.0,bar依赖lodash@^1.0.0,则依据semver的规则,二者不存在兼容版本。会将一个版本放在node_modules中,另一个仍保留在依赖树里。

举个例子,假设一个依赖树原本是这样:
node_modules
-- foo
---- lodash@version1

-- bar
---- lodash@version2

假设 version1 和 version2 是兼容版本,则经过 dedupe 会成为下面的形式:

node_modules
-- foo

-- bar

-- lodash(保留的版本为兼容版本)

假设 version1 和 version2 为非兼容版本,则后面的版本保留在依赖树中:

node_modules
-- foo
-- lodash@version1

-- bar
---- lodash@version2

5、安装模块

这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。

6、执行工程自身生命周期

当前npm工程如果定义了钩子此时会被执行(按照install、postinstall、prepublish、prepare 的顺序)。
最后一步是生成或更新版本描述文件,npm install 过程完成。

结语

摘自前端壹题侵权删