npm模块安装机制与实现原理

从 npm install 说起

npm install 命令用来安装模块到 node_modules 目录

npm install <packageName>

安装之前,npm install 会先检查, node_modules 目录之中是否已经存在指定模块。如果存在就不再重新安装了,即使远程仓库已经有了一个新版本,也是如此。

如果你希望,一个模块不管是否安装过, npm 都要强制重新安装,可以使用 -f--force 参数。

npm install <packageName> --force

npm update

如果想更新已安装模块,就要用到 npm update 命令

npm update <packageName>

它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。

registry

npm update 命令怎么知道每个模块的最新版本呢?

答案是 npm 模块仓库提供了一个查询服务,叫做 registry。以 npmjs.org 为例,它的查询服务网址是 https://registry.npmjs.org/

这个网址后面跟上模块名,就会得到一个 JSON 对象,里面是改模块所有版本的信息。比如访问 https://registry.npmjs.org/react ,就会看到 react 模块所有版本的信息。

它跟下面命令的效果是一样的:

npm view react
npm info react
npm show react
npm v react

registry 网址的模块名后面,还可以跟上版本号或标签,用来查询某个具体版本的信息。比如,访问 https://registry.npmjs.org/react/v0.14.6 ,就可以看到 React 的 0.14.6 版。

·返回的 JSON 对象里面,有一个 dist.tarball 属性,是该版本压缩包的网址:

dist: {
  shasum: '2a57c2cf8747b483759ad8de0fa47fb0c5cf5c6a',
  tarball: 'http://registry.npmjs.org/react/-/react-0.14.6.tgz'
}

到这个网址下载压缩包,在本地解压,就得到了模块的源码。npm installnpm update 命令都是通过这种方式安装模块的。

缓存目录

npm installnpm update 命令从 registry 下载压缩包之后,都存放在本地的缓存目录。

这个缓存目录在 Linux 或 Mac 默认是用用户主目录下的 .npm 目录,在 Windows 默认是 %AppData%/npm-cache 。通过配置命令可以查看这个目录的具体位置:

npm config get cache

可以浏览一下这个目录:

npm cache ls XXX

你会看到里面存放着大量的模块,储存结构是 { cache } / { name } / { version }

$ npm cache ls react
~/.npm/react/react/0.14.6/
~/.npm/react/react/0.14.6/package.tgz
~/.npm/react/react/0.14.6/package/
~/.npm/react/react/0.14.6/package/package.json

每个模块的每个版本都有一个自己的子目录,里面是代码的压缩包 package.tgz 文件以及一个描述文件 package/package.json

除此之外,还会生成一个 {cache}/{hostname}/{path}/.cache.json 文件。比如,从 npm 官方仓库下载 react 模块的时候,就会生成 registry.npmjs.org/react/.cache.json 文件。这个文件保存的是所有版本的信息,以及该模块最近修改的时间和最新一次请求时服务器返回的 ETag

{
  "time":{
    "modified":"2016-01-06T23:52:45.571Z",
    // ...
  },
  "_etag":"\"7S37I0775YLURCFIO8N85FO0F\""
}

对于一些不是很关键的操作(比如 npm searchnpm view ),npm会先查看 .cache.json 里面的模块最近更新时间,跟当前时间的差距,是不是在可接受的范围之内。如果是的,就不再向远程仓库发出请求,而是直接返回 .cache.json 的数据。

.npm 目录保存着大量文件,清空它的命令为:

$ rm -rf ~/.npm/*
# 或者
$ npm cache clean

模块的安装过程

总结一下。Node 模块的安装过程是这样的:

  1. 发出 npm install 命令
  2. npm 向 registry 查询模块压缩包的网址
  3. 下载压缩包,存放在 ~/.npm 目录
  4. 解压压缩包到当前项目的node_modules

注意,一个模块安装以后,本地其实保存了两份。一份是 ~/.npm 目录下的压缩包,另一份是 node_modules 目录下解压后的代码。

但是运行 npm install 的时候,只会检查 node_modules 目录,而不会检查 ~/.npm 目录。也就是说如果一个模块在 ~/.npm 下有压缩包,但是没有安装在 node_modules 目录中,npm 依然会从远程仓库下载一次新的压缩包。

npm 实现原理

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

  1. 执行工程自身 preinstall
    当前 npm 工程如果定义了 preinstall 钩子此时会被执行。
  2. 确定首层依赖模块
    首先需要做的是确定工程中的首层依赖,也就是 dependencies 和 devDependencies 属性中直接指定的模块。
    工程本身是整棵依赖树的根节点,每个首层依赖模块都是根节点下面的一棵子树,npm 会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。
  3. 获取模块
    获取模块的一个递归的过程,分为以下几步:
    1.获取模块信息。在下载一个模块之前,首先要确定其版本,此时如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有该模块信息直接拿即可,如果没有则从仓库获取。如 packaeg.json 中某个包的版本是 ^1.1.0,npm 就会去仓库中获取符合 1.x.x 形式的最新版本。
    2.获取模块实体。上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。
    3.查找该模块依赖,如果有依赖则回到第1步,如果没有则停止。
  4. 模块扁平化(dedupe)
    上一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。比如 A 模块依赖于 loadsh,B 模块同样依赖于 lodash。在 npm3 以前会严格按照依赖树的结构进行安装,因此会造成模块冗余。

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

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

  1. 安装模块
    这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。
  2. 执行工程自身生命周期
    当前 npm 工程如果定义了钩子此时会被执行(按照 install、postinstall、prepublish、prepare 的顺序)。

最后一步是生成或更新版本描述文件,npm install 过程完成。

来源:《npm 模块安装机制简介》
作者: 阮一峰

来源:《npm 模块安装机制,为什么输入 npm install 就可以自动安装对应的模块?》
作者:紫微前端

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值