深挖 NPM 机制

使用NPM安装的时候会常常出现包冲突(好比多个主模块的子模块版本不一致等),致使在开发过程当中会遇到各类或大或小的问题。全部在这会介绍如下内容:html

  1. NPM 主要安装方式
  2. NPM 包信息查询
  3. NPM 安装机制(主要)

安装&查询命令

NPM 各类安装方式node

  • npm install packageName[@next | @versionNumber]git

    • 在 node_modules 中没有指定模块时安装,(不检查~/.npm目录)
  • npm install packageName --f | -- forcegithub

    • 一个模块不论是否安装过,npm都要 强制从新安装
  • npm update packageNamenpm

    • 若是远程版本较新、或者本地版本不存在时安装

NPM 查询服务json

  • NPM经过registry的查询服务,从而知道每一个模块的最新版本。
  • 能够经过 npm view packageName [version] 查询对映模块的信息

NPM 安装机制

输入 npm install 命令并敲下回车后,会经历以下几个阶段(以 npm 5.5.1 为例):缓存

1. 执行工程自身 preinstall函数

当前 npm 工程若是定义了 preinstall 钩子此时会被执行。post

2. 肯定首层依赖模块code

首先须要作的是肯定工程中的首层依赖,也就是 dependenciesdevDependencies 属性中直接指定的模块(假设此时没有添加 npm install 参数)。

工程自己是整棵依赖树的根节点,每一个首层依赖模块都是根节点下面的一棵子树,npm 会开启多进程从每一个首层依赖模块开始逐步寻找更深层级的节点。

若是查询node_modules目录之中已经存在指定模块,那么再也不从新安装

3. 获取模块

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

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

4. 模块扁平化(dedupe)

一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。好比 A 模块依赖于 loadsh,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.1.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 过程完成。

End

文章分享同步于: github.com/zhongmeizhi…

参考

相关文章
相关标签/搜索