从输入 npm install 后

  1. 项目下有 package.json,输入npm i,查询 node_modules 是否存在,不存在新增,存在在此下安装模块。

  2. 根据 package.json 的依赖模块,查询 node_modules 目录之中是否已经存在指定模块,若存在,不再重新安装,若不存在, npm 向 registry 查询模块压缩包的网址,下载压缩包,存放在本地的缓存目录里,解压压缩包到当前项目的 node_modules 目录。

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

    1
    npm config get cache
  3. 获取模块,根据 dependencies 和 devDependencies 属性中的模块,递归获取,既工程本身是整棵依赖树的根节点(首层),每个首层依赖模块都是根节点下面的一棵子树,npm 会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。

    1. 获取模块信息。在下载一个模块之前,首先要确定其版本,这是因为 package.json 中往往是 semantic version(semver,语义化版本)。此时如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有该模块信息直接拿即可,如果没有则从仓库获取。如 package.json 中某个包的版本是 ^1.1.0,npm 就会去仓库中获取符合 1.x.x 形式的最新版本。

    2. 获取模块实体。上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。

    3. 查找该模块依赖,如果有依赖则回到第 1 步,如果没有则停止。

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

缓存目录

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

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

1
npm config get cache

缓存目录可以加速某些操作如npm search或npm view都会先去缓存目录里查询,然后查询不符合条件的情况下才会请求远程仓库。

由于缓存目录的存在,我们可以使用 npm 的下载缓存包来实现离线下载的功能,这个就不描述。

但由于有了package-lock.json后,我们会经常遇到一些由于缓存问题而无法下载包的问题,因为npm install之后会计算每个包的 sha1 值,然后将包与他的 sha1 值关联保存在 package-lock.json 里面,下次 npm install 的时候会根据 package-lock.json 里面保存的 sha1 值去文件夹 %AppData%/npm-cache 里面寻找包文件,如果存在,就不用再次从远程仓库下载。

因为 npm 不同版本算出来的 sha1 有可能不完全一样,所以 npm 升级了,直接用不同版本 npm 生成的package-lock.json会报 sha1 不匹配的 error。

清除缓存

npm cache verify
重新计算%AppData%/npm-cache 下的文件是否与 sha1 值匹配,如果不匹配可能删除。

npm cache clean --force
删除所有缓存文件

注意:
npm 5 使用了新的包管理模式,所以在升级之后,请先清空一下本地缓存

1
2
3
4
npm cache clean --force

// 清除失败,使用这个
npm cache clear --force && npm install --no-shrinkwrap --update-binary

模块扁平化

由于 npm2 安装多级的依赖模块采用嵌套的安装方式,可能造成相同模块大量冗余的问题。
所以 npm3 后会尽量把逻辑上某个层级的模块在物理结构上全部放在项目的第一层级(首层)里。

具体概括为以下三种情况:

  1. 在安装某个二级模块时,若发现第一层级还没有相同名称的模块,便把这第二层级的模块放在第一层级
  2. 在安装某个二级模块时,若发现第一层级有相同名称,相同版本的模块,便直接复用那个模块
  3. 在安装某个二级模块时,若发现第一层级有相同名称,但版本不同的模块,便只能嵌套在自身的父模块下方,既在当前二级模块下生成一个 node_modules 存放依赖模块。
2
2

实际上:npm3 后 仍然可能出现模块冗余的情况,如因为一级目录下已经有 v1.0 的 C 模块了,所以所有的 v2.0 只能作为二级依赖模块被安装,这样你就会看到如下的情况

1
1

而这种怎么解决了,我们要把依赖 v1.0 的模块更新到 v2.0,然后npm dedupe把 v2.0 放置到一个目录里,去除冗余模块

3
3

重复模块定义

它指的是模块名相同且 semver 兼容。每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在 dedupe 过程中被去掉。
因为package.json里的依赖包默认情况下使用^,这只能确定大版本相同,而这也导致每次根据package.json安装依赖时依赖包有版本偏差的问题,如作者修改一个 BUG,发布了个小版本,而你刚刚好又重新安装了,然后你没有问题,而你同事没有安装,出现 BUG,但从package.json上是无法体现的,在不了解^的情况下是很难排查的,但你可以根据package-lock.json知道你那个包更新了、和你同事不一致或在项目下载依赖时都根据package-lock.json去下载。

npm ci

1
npm ci

该命令与相似 npm-install,不同之处在于它用于自动化环境(例如测试平台,持续集成和部署),或者在任何情况下都要确保干净安装依赖项。通过跳过某些面向用户的功能,它可以比常规的 npm 安装快得多。它也比常规安装更加严格,这可以帮助捕获大多数 npm 用户增量安装的本地环境引起的错误或不一致。

简而言之,使用 npm install 和之间的主要区别 npm ci 是:

  1. 该项目必须具有现有的 package-lock.json 或 npm-shrinkwrap.json。
  2. 如果程序包锁中的依赖项与中的不匹配 package.json,npm ci 则将退出并显示错误,而不是更新程序包锁。
  3. npm ci 一次只能安装整个项目:不能使用此命令添加单个依赖项。
  4. 如果 node_modules 已经存在,它将在 npm ci 开始安装之前自动删除。
  5. 它将永远不会写入 package.json 或执行任何软件包锁:安装实际上是冻结的。

npm 模块安装机制简介
npm install 的实现原理