npm包---前世此生(二)

**npm管理机制**

Nodejs成功离不开 npm 优秀的依赖管理系统。在介绍整个依赖系统以前,必需要了解 npm如何管理依赖包的版本,本章将介绍 npm包 的版本发布规范、如何管理各类依赖包的版本以及一些关于包版本的最佳实践。html

![](https://pic4.zhimg.com/80/v2-93e39e7ffb7927172ca314f24c6381a6_1440w.jpg)

2.1 查看npm包版本

你能够执行 npm view package version 查看某个 package 的最新版本。node

执行 npm view conard versions 查看某个 package 在npm服务器上全部发布过的版本。react

![](https://pic3.zhimg.com/80/v2-455593cafdefd078aa93a73c3e660e1b_1440w.jpg)

执行 npm ls 可查看当前仓库依赖树上全部包的版本信息。git

![](https://pic3.zhimg.com/80/v2-ee4a5aaa37db08a924f5aaf4de719589_1440w.jpg)

2.2 SemVer规范

npm包 中的模块版本都须要遵循 SemVer规范——由 Github 起草的一个具备指导意义的,统一的版本号表示规则。实际上就是 Semantic Version(语义化版本)的缩写。github

SemVer规范官网: semver.org/docker

标准版本

SemVer规范的标准版本号采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每一个元素必须以数值来递增。npm

  • 主版本号(major):当你作了不兼容的API 修改
  • 次版本号(minor):当你作了向下兼容的功能性新增
  • 修订号(patch):当你作了向下兼容的问题修正。

例如:1.9.1 -> 1.10.0 -> 1.11.0json

先行版本

当某个版本改动比较大、并不是稳定并且可能没法知足预期的兼容性需求时,你可能要先发布一个先行版本。缓存

先行版本号能够加到“主版本号.次版本号.修订号”的后面,先加上一个链接号再加上一连串以句点分隔的标识符和版本编译信息。安全

  • 内部版本(alpha):
  • 公测版本(beta):
  • 正式版本的候选版本rc: 即 Release candiate

React的版本

下面咱们来看看 React 的历史版本:

![](https://pic3.zhimg.com/v2-0085428d8f8f6bfa9de2f2ccdcebfc63_b.jpg)

可见是严格按照 SemVer 规范来发版的:

  • 版本号严格按照 主版本号.次版本号.修订号 格式命名
  • 版本是严格递增的,:16.8.0 -> 16.8.1 -> 16.8.2
  • 发布重大版本或版本改动较大时,先发布alphabetarc等先行版本

发布版本

在修改 npm 包某些功能后一般须要发布一个新的版本,咱们一般的作法是直接去修改 package.json 到指定版本。若是操做失误,很容易形成版本号混乱,咱们能够借助符合 Semver 规范的命令来完成这一操做:

  • npm version patch : 升级修订版本号
  • npm version minor : 升级次版本号
  • npm version major : 升级主版本号

2.3 版本工具使用

在开发中确定少不了对一些版本号的操做,若是这些版本号符合 SemVer规范 ,咱们能够借助用于操做版本的npm包semver来帮助咱们进行比较版本大小、提取版本信息等操做。

Npm 也使用了该工具来处理版本相关的工做。

npm install semver
复制代码
  • 比较版本号大小
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
复制代码
  • 判断版本号是否符合规范,返回解析后符合规范的版本号。
semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
复制代码
  • 将其余版本号强制转换成semver版本号
semver.valid(semver.coerce('v2')) // '2.0.0'
semver.valid(semver.coerce('42.6.7.9.3-alpha')) // '42.6.7'
复制代码
  • 一些其余用法
semver.clean('  =v1.2.3   ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.minVersion('>=1.0.0') // '1.0.0'
复制代码

以上都是semver最多见的用法,更多详细内容能够查看 semver文档:github.com/npm/node-se…

2.4 依赖版本管理

咱们常常看到,在 package.json 中各类依赖的不一样写法:

"dependencies": {
    "signale": "1.4.0",
    "figlet": "*",
    "react": "16.x",
    "table": "~5.4.6",
    "yargs": "^14.0.0"
  }
复制代码

前面三个很容易理解:

  • "signale": "1.4.0": 固定版本号
  • "figlet": "*": 任意版本(>=0.0.0
  • "react": "16.x": 匹配主要版本(>=16.0.0 <17.0.0
  • "react": "16.3.x": 匹配主要版本和次要版本(>=16.3.0 <16.4.0

再来看看后面两个,版本号中引用了 ~^ 符号:

  • ~: 当安装依赖时获取到有新版本时,安装到 x.y.zz 的最新的版本。即保持主版本号、次版本号不变的状况下,保持修订号的最新版本。
  • ^: 当安装依赖时获取到有新版本时,安装到 x.y.zyz 都为最新版本。 即保持主版本号不变的状况下,保持次版本号、修订版本号为最新版本。

package.json 文件中最多见的应该是 "yargs": "^14.0.0" 这种格式的 依赖, 由于咱们在使用 npm install package 安装包时,npm 默认安装当前最新版本,而后在所安装的版本号前加 ^ 号。

注意,当主版本号为 0 的状况,会被认为是一个不稳定版本,状况与上面不一样:

  • 主版本号和次版本号都为 0: ^0.0.z~0.0.z 都被看成固定版本,安装依赖时均不会发生变化。
  • 主版本号为 0: ^0.y.z 表现和 ~0.y.z 相同,只保持修订号为最新版本。

1.0.0 的版本号用于界定公共 API。当你的软件发布到了正式环境,或者有稳定的API时,就能够发布1.0.0版本了。因此,当你决定对外部发布一个正式版本的npm包时,把它的版本标为1.0.0。

2.5 锁定依赖版本

lock文件

实际开发中,常常会由于各类依赖不一致而产生奇怪的问题,或者在某些场景下,咱们不但愿依赖被更新,建议在开发中使用 package-lock.json

锁定依赖版本意味着在咱们不手动执行更新的状况下,每次安装依赖都会安装固定版本。保证整个团队使用版本号一致的依赖。

每次安装固定版本,无需计算依赖版本范围,大部分场景下能大大加速依赖安装时间。

使用 package-lock.json 要确保npm的版本在5.6以上,由于在5.0 - 5.6中间,对 package-lock.json的处理逻辑进行过几回更新,5.6版本后处理逻辑逐渐稳定。

关于 package-lock.json 详细的结构,咱们会在后面的章节进行解析。

按期更新依赖

咱们的目的是保证团队中使用的依赖一致或者稳定,而不是永远不去更新这些依赖。实际开发场景下,咱们虽然不须要每次都去安装新的版本,仍然须要定时去升级依赖版本,来让咱们享受依赖包升级带来的问题修复、性能提高、新特性更新。

![](https://picb.zhimg.com/80/v2-3fba91503855869cce7f0d2816a45217_1440w.jpg)

使用 npm outdated 能够帮助咱们列出有哪些尚未升级到最新版本的依赖:

  • 黄色表示不符合咱们指定的语意化版本范围 - 不须要升级
  • 红色表示符合指定的语意化版本范围 - 须要升级

执行 npm update 会升级全部的红色依赖。

2.6 依赖版本选择的最佳实践

版本发布

  • 对外部发布一个正式版本的npm包时,把它的版本标为1.0.0
  • 某个包版本发行后,任何修改都必须以新版本发行。
  • 版本号严格按照 主版本号.次版本号.修订号 格式命名
  • 版本号发布必须是严格递增的
  • 发布重大版本或版本改动较大时,先发布alpha、beta、rc等先行版本

依赖范围选择

  • 主工程依赖了不少子模块,都是团队成员开发的npm包,此时建议把版本前缀改成~,若是锁定的话每次子依赖更新都要对主工程的依赖进行升级,很是繁琐,若是对子依赖彻底信任,直接开启^每次升级到最新版本。
  • 主工程跑在docker线上,本地还在进行子依赖开发和升级,在docker版本发布前要锁定全部依赖版本,确保本地子依赖发布后线上不会出问题。

保持依赖一致

  • 确保npm的版本在5.6以上,确保默认开启 package-lock.json 文件。
  • 由初始化成员执行 npm inatall 后,将 package-lock.json 提交到远程仓库。不要直接提交 node_modules到远程仓库。
  • 按期执行 npm update 升级依赖,并提交 lock 文件确保其余成员同步更新依赖,不要手动更改 lock 文件。

依赖变动

  • 升级依赖: 修改 package.json文件的依赖版本,执行 npm install
  • 降级依赖: 直接执行 npm install package@version(改动package.json不会对依赖进行降级)
  • 注意改动依赖后提交lock文件

3、剖析 npm install 原理

![](https://picb.zhimg.com/80/v2-fb73956086aae2f5908bbd8410fbeb53_1440w.jpg)

npm install 大概会通过上面的几个流程,这一章就来说一讲各个流程的实现细节、发展以及为什么要这样实现。

3.1 嵌套结构

咱们都知道,执行 npm install 后,依赖包被安装到了 node_modules ,下面咱们来具体了解下,npm 将依赖包安装到 node_modules 的具体机制是什么。

npm 的早期版本, npm 处理依赖的方式简单粗暴,以递归的形式,严格按照 package.json 结构以及子依赖包的 package.json 结构将依赖安装到他们各自的 node_modules 中。直到有子依赖包不在依赖其余模块。

举个例子,咱们的模块 my-app 如今依赖了两个模块:bufferignore

{
  "name": "my-app",
  "dependencies": {
    "buffer": "^5.4.3",
    "ignore": "^5.1.4",
  }
}
复制代码

ignore是一个纯 JS 模块,不依赖任何其余模块,而 buffer 又依赖了下面两个模块:base64-jsieee754

{
  "name": "buffer",
  "dependencies": {
    "base64-js": "^1.0.2",
    "ieee754": "^1.1.4"
  }
}
复制代码

那么,执行 npm install 后,获得的 node_modules 中模块目录结构就是下面这样的:

![](https://pic2.zhimg.com/80/v2-d7da835e621c0579241326f45c08269b_1440w.jpg)

这样的方式优势很明显, node_modules 的结构和 package.json 结构一一对应,层级结构明显,而且保证了每次安装目录结构都是相同的。

可是,试想一下,若是你依赖的模块很是之多,你的 node_modules 将很是庞大,嵌套层级很是之深:

![](https://pic1.zhimg.com/80/v2-8eca811ae31469bc34199c4787ab2f5c_1440w.jpg)
  • 在不一样层级的依赖中,可能引用了同一个模块,致使大量冗余。
  • Windows 系统中,文件路径最大长度为260个字符,嵌套层级过深可能致使不可预知的问题。

3.2 扁平结构

为了解决以上问题,NPM3.x 版本作了一次较大更新。其将早期的嵌套结构改成扁平结构:

  • 安装模块时,无论其是直接依赖仍是子依赖的依赖,优先将其安装在 node_modules 根目录。

仍是上面的依赖结构,咱们在执行 npm install 后将获得下面的目录结构:

![](https://pic1.zhimg.com/80/v2-2f49bf3c2b697738f973871723974437_1440w.jpg)
![](https://pic1.zhimg.com/80/v2-8a6918b1d37ec0adbc8f3bb09a62e7a7_1440w.jpg)

此时咱们若在模块中又依赖了 base64-js@1.0.1 版本:

{
  "name": "my-app",
  "dependencies": {
    "buffer": "^5.4.3",
    "ignore": "^5.1.4",
    "base64-js": "1.0.1",
  }
}
复制代码
  • 当安装到相同模块时,判断已安装的模块版本是否符合新模块的版本范围,若是符合则跳过,不符合则在当前模块的 node_modules 下安装该模块。

此时,咱们在执行 npm install 后将获得下面的目录结构:

![](https://pic4.zhimg.com/80/v2-d842224747e17bd2512174de4fc8d723_1440w.jpg)
![](https://pic1.zhimg.com/80/v2-08abe604de74c679f2ee4ce702aa9e34_1440w.jpg)

对应的,若是咱们在项目代码中引用了一个模块,模块查找流程以下:

  • 在当前模块路径下搜索
  • 在当前模块 node_modules 路径下搜素
  • 在上级模块的 node_modules 路径下搜索
  • ...
  • 直到搜索到全局路径中的 node_modules

假设咱们又依赖了一个包 buffer2@^5.4.3,而它依赖了包 base64-js@1.0.3,则此时的安装结构是下面这样的:

![](https://pic2.zhimg.com/80/v2-9ee4ba8b5a28f91619b689cafed32b7d_1440w.jpg)

因此 npm 3.x 版本并未彻底解决老版本的模块冗余问题,甚至还会带来新的问题。

试想一下,你的APP假设没有依赖 base64-js@1.0.1 版本,而你同时依赖了依赖不一样 base64-js 版本的 bufferbuffer2。因为在执行 npm install 的时候,按照 package.json 里依赖的顺序依次解析,则 bufferbuffer2package.json 的放置顺序则决定了 node_modules 的依赖结构:

先依赖buffer2

![](https://picb.zhimg.com/80/v2-57c40745ff1ef48b2465b6e8861cdec3_1440w.jpg)

先依赖buffer

![](https://pic2.zhimg.com/80/v2-eb4099221631e091a0ffe87a5a804a8e_1440w.jpg)

另外,为了让开发者在安全的前提下使用最新的依赖包,咱们在 package.json 一般只会锁定大版本,这意味着在某些依赖包小版本更新后,一样可能形成依赖结构的改动,依赖结构的不肯定性可能会给程序带来不可预知的问题。

3.3 Lock文件

为了解决 npm install 的不肯定性问题,在 npm 5.x 版本新增了 package-lock.json 文件,而安装方式还沿用了 npm 3.x 的扁平化的方式。

package-lock.json 的做用是锁定依赖结构,即只要你目录下有 package-lock.json 文件,那么你每次执行 npm install 后生成的 node_modules 目录结构必定是彻底相同的。

例如,咱们有以下的依赖结构:

{
  "name": "my-app",
  "dependencies": {
    "buffer": "^5.4.3",
    "ignore": "^5.1.4",
    "base64-js": "1.0.1",
  }
}
复制代码

在执行 npm install 后生成的 package-lock.json 以下:

{
  "name": "my-app",
  "version": "1.0.0",
  "dependencies": {
    "base64-js": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz",
      "integrity": "sha1-aSbRsZT7xze47tUTdW3i/Np+pAg="
    },
    "buffer": {
      "version": "5.4.3",
      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz",
      "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==",
      "requires": {
        "base64-js": "^1.0.2",
        "ieee754": "^1.1.4"
      },
      "dependencies": {
        "base64-js": {
          "version": "1.3.1",
          "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
          "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
        }
      }
    },
    "ieee754": {
      "version": "1.1.13",
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
    },
    "ignore": {
      "version": "5.1.4",
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
      "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A=="
    }
  }
}
复制代码

咱们来具体看看上面的结构:

![](https://pic1.zhimg.com/80/v2-4aa8fa0e62c4a026111bf7801cd7c77c_1440w.jpg)

最外面的两个属性 nameversionpackage.json 中的 nameversion ,用于描述当前包名称和版本。

dependencies 是一个对象,对象和 node_modules 中的包结构一一对应,对象的 key 为包名称,值为包的一些描述信息:

  • version:包版本 —— 这个包当前安装在 node_modules 中的版本
  • resolved:包具体的安装来源
  • integrity:包 hash 值,基于 Subresource Integrity 来验证已安装的软件包是否被改动过、是否已失效
  • requires:对应子依赖的依赖,与子依赖的 package.jsondependencies的依赖项相同。
  • dependencies:结构和外层的 dependencies 结构相同,存储安装在子依赖 node_modules 中的依赖包。

这里注意,并非全部的子依赖都有 dependencies 属性,只有子依赖的依赖和当前已安装在根目录的 node_modules 中的依赖冲突以后,才会有这个属性。

例如,回顾下上面的依赖关系:

![](https://pic1.zhimg.com/80/v2-08abe604de74c679f2ee4ce702aa9e34_1440w.jpg)

咱们在 my-app 中依赖的 base64-js@1.0.1 版本与 buffer 中依赖的 base64-js@^1.0.2 发生冲突,因此 base64-js@1.0.1 须要安装在 buffer 包的 node_modules 中,对应了 package-lock.jsonbufferdependencies 属性。这也对应了 npm 对依赖的扁平化处理方式。

因此,根据上面的分析, package-lock.json 文件 和 node_modules 目录结构是一一对应的,即项目目录下存在 package-lock.json 可让每次安装生成的依赖目录结构保持相同。

另外,项目中使用了 package-lock.json 能够显著加速依赖安装时间。

咱们使用 npm i --timing=true --loglevel=verbose 命令能够看到 npm install 的完整过程,下面咱们来对比下使用 lock 文件和不使用 lock 文件的差异。在对比前先清理下npm 缓存。

不使用 lock 文件:

![](https://picb.zhimg.com/v2-3de6cffeec99e99371f0348da81035ec_b.jpg)

使用 lock 文件:

![](https://picb.zhimg.com/v2-544d2d5af4d191ccc8c102f56c29b135_b.jpg)

可见, package-lock.json 中已经缓存了每一个包的具体版本和下载连接,不须要再去远程仓库进行查询,而后直接进入文件完整性校验环节,减小了大量网络请求。

使用建议

开发系统应用时,建议把 package-lock.json 文件提交到代码版本仓库,从而保证全部团队开发者以及 CI 环节能够在执行 npm install 时安装的依赖版本都是一致的。

在开发一个 npm包 时,你的 npm包 是须要被其余仓库依赖的,因为上面咱们讲到的扁平安装机制,若是你锁定了依赖包版本,你的依赖包就不能和其余依赖包共享同一 semver 范围内的依赖包,这样会形成没必要要的冗余。因此咱们不该该把package-lock.json 文件发布出去( npm 默认也不会把 package-lock.json 文件发布出去)。

3.4 缓存

在执行 npm installnpm update命令下载依赖后,除了将依赖包安装在node_modules 目录下外,还会在本地的缓存目录缓存一份。

经过 npm config get cache 命令能够查询到:在 LinuxMac 默认是用户主目录下的 .npm/_cacache 目录。

在这个目录下又存在两个目录:content-v2index-v5content-v2 目录用于存储 tar包的缓存,而index-v5目录用于存储tar包的 hash

npm 在执行安装时,能够根据 package-lock.json 中存储的 integrity、version、name 生成一个惟一的 key 对应到 index-v5 目录下的缓存记录,从而找到 tar包的 hash,而后根据 hash 再去找缓存的 tar包直接使用。

咱们能够找一个包在缓存目录下搜索测试一下,在 index-v5 搜索一下包路径:

grep "https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz" -r index-v5
复制代码
![](https://picb.zhimg.com/80/v2-9ee25a740512d4ae457d48d31b643eb7_1440w.jpg)

而后咱们将json格式化:

{
  "key": "pacote:version-manifest:https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz:sha1-aSbRsZT7xze47tUTdW3i/Np+pAg=",
  "integrity": "sha512-C2EkHXwXvLsbrucJTRS3xFHv7Mf/y9klmKDxPTE8yevCoH5h8Ae69Y+/lP+ahpW91crnzgO78elOk2E6APJfIQ==",
  "time": 1575554308857,
  "size": 1,
  "metadata": {
    "id": "base64-js@1.0.1",
    "manifest": {
      "name": "base64-js",
      "version": "1.0.1",
      "engines": {
        "node": ">= 0.4"
      },
      "dependencies": {},
      "optionalDependencies": {},
      "devDependencies": {
        "standard": "^5.2.2",
        "tape": "4.x"
      },
      "bundleDependencies": false,
      "peerDependencies": {},
      "deprecated": false,
      "_resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz",
      "_integrity": "sha1-aSbRsZT7xze47tUTdW3i/Np+pAg=",
      "_shasum": "6926d1b194fbc737b8eed513756de2fcda7ea408",
      "_shrinkwrap": null,
      "bin": null,
      "_id": "base64-js@1.0.1"
    },
    "type": "finalized-manifest"
  }
}
复制代码

上面的 _shasum 属性 6926d1b194fbc737b8eed513756de2fcda7ea408 即为 tar 包的 hashhash的前几位 6926 即为缓存的前两层目录,咱们进去这个目录果真找到的压缩后的依赖包:

![](https://picb.zhimg.com/80/v2-f3fb960446d2d3ee3bc9f89824405bdf_1440w.jpg)

以上的缓存策略是从 npm v5 版本开始的,在 npm v5 版本以前,每一个缓存的模块在 ~/.npm 文件夹中以模块名的形式直接存储,储存结构是{cache}/{name}/{version}。

npm 提供了几个命令来管理缓存数据:

  • npm cache add:官方解释说这个命令主要是 npm 内部使用,可是也能够用来手动给一个指定的 package 添加缓存。
  • npm cache clean:删除缓存目录下的全部数据,为了保证缓存数据的完整性,须要加上 --force 参数。
  • npm cache verify:验证缓存数据的有效性和完整性,清理垃圾数据。

基于缓存数据,npm 提供了离线安装模式,分别有如下几种:

  • --prefer-offline: 优先使用缓存数据,若是没有匹配的缓存数据,则从远程仓库下载。
  • --prefer-online: 优先使用网络数据,若是网络数据请求失败,再去请求缓存数据,这种模式能够及时获取最新的模块。
  • --offline: 不请求网络,直接使用缓存数据,一旦缓存数据不存在,则安装失败。

3.5 文件完整性

上面咱们屡次提到了文件完整性,那么什么是文件完整性校验呢?

在下载依赖包以前,咱们通常就能拿到 npm 对该依赖包计算的 hash 值,例如咱们执行 npm info 命令,紧跟 tarball(下载连接) 的就是 shasum(hash) :

![](https://pic4.zhimg.com/80/v2-c91b4b2cfe672ff7c8db55592fc64224_1440w.jpg)

用户下载依赖包到本地后,须要肯定在下载过程当中没有出现错误,因此在下载完成以后须要在本地在计算一次文件的 hash 值,若是两个 hash 值是相同的,则确保下载的依赖是完整的,若是不一样,则进行从新下载。

3.6 总体流程

好了,咱们再来总体总结下上面的流程:

  • 检查 .npmrc 文件:优先级为:项目级的 .npmrc 文件 > 用户级的 .npmrc 文件> 全局级的 .npmrc 文件 > npm 内置的 .npmrc 文件

  • 检查项目中有无 lock 文件。

  • lock 文件:

  • npm 远程仓库获取包信息

  • 根据 package.json 构建依赖树,构建过程:

  • 构建依赖树时,无论其是直接依赖仍是子依赖的依赖,优先将其放置在 node_modules 根目录。

  • 当遇到相同模块时,判断已放置在依赖树的模块版本是否符合新模块的版本范围,若是符合则跳过,不符合则在当前模块的 node_modules 下放置该模块。

  • 注意这一步只是肯定逻辑上的依赖树,并不是真正的安装,后面会根据这个依赖结构去下载或拿到缓存中的依赖包

  • 在缓存中依次查找依赖树中的每一个包

  • 不存在缓存:

  • npm 远程仓库下载包

  • 校验包的完整性

  • 校验不经过:

  • 从新下载

  • 校验经过:

  • 将下载的包复制到 npm 缓存目录

  • 将下载的包按照依赖结构解压到 node_modules

  • 存在缓存:将缓存按照依赖结构解压到 node_modules

  • 将包解压到 node_modules

  • 生成 lock 文件

  • lock 文件:

  • 检查 package.json 中的依赖版本是否和 package-lock.json 中的依赖有冲突。

  • 若是没有冲突,直接跳过获取包信息、构建依赖树过程,开始在缓存中查找包信息,后续过程相同

![](https://picb.zhimg.com/80/v2-fb73956086aae2f5908bbd8410fbeb53_1440w.jpg)

上面的过程简要描述了 npm install 的大概过程,这个过程还包含了一些其余的操做,例如执行你定义的一些生命周期函数,你能够执行 npm install package --timing=true --loglevel=verbose 来查看某个包具体的安装流程和细节。

3.7 yarn

![](https://picb.zhimg.com/80/v2-44c3ecdb7ac0fbcae1d37eb8f76bfc24_1440w.jpg)

yarn 是在 2016 年发布的,那时 npm 还处于 V3 时期,那时候尚未 package-lock.json 文件,就像上面咱们提到的:不稳定性、安装速度慢等缺点常常会受到广大开发者吐槽。此时,yarn 诞生:

![](https://pic3.zhimg.com/80/v2-6ccdf2832eb5d94dce06efbf61aac8b1_1440w.jpg)

上面是官网提到的 yarn 的优势,在那个时候仍是很是吸引人的。固然,后来 npm 也意识到了本身的问题,进行了不少次优化,在后面的优化(lock文件、缓存、默认-s...)中,咱们多多少少能看到 yarn 的影子,可见 yarn 的设计仍是很是优秀的。

yarn 也是采用的是 npm v3 的扁平结构来管理依赖,安装依赖后默认会生成一个 yarn.lock 文件,仍是上面的依赖关系,咱们看看 yarn.lock 的结构:

# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

base64-js@1.0.1:
  version "1.0.1"
  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.0.1.tgz#6926d1b194fbc737b8eed513756de2fcda7ea408"
  integrity sha1-aSbRsZT7xze47tUTdW3i/Np+pAg=

base64-js@^1.0.2:
  version "1.3.1"
  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
  integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==

buffer@^5.4.3:
  version "5.4.3"
  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115"
  integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==
  dependencies:
    base64-js "^1.0.2"
    ieee754 "^1.1.4"

ieee754@^1.1.4:
  version "1.1.13"
  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
  integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==

ignore@^5.1.4:
  version "5.1.4"
  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
  integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==
复制代码

可见其和 package-lock.json 文件仍是比较相似的,还有一些区别就是:

  • package-lock.json 使用的是 json 格式,yarn.lock 使用的是一种自定义格式
  • yarn.lock 中子依赖的版本号不是固定的,意味着单独又一个 yarn.lock 肯定不了 node_modules 目录结构,还须要和 package.json 文件进行配合。而 package-lock.json 只须要一个文件便可肯定。

yarn 的缓策略看起来和 npm v5 以前的很像,每一个缓存的模块被存放在独立的文件夹,文件夹名称包含了模块名称、版本号等信息。使用命令 yarn cache dir 能够查看缓存数据的目录:

![](https://pic2.zhimg.com/80/v2-8c0786d57b357bcfdff92d2774754e76_1440w.jpg)

yarn 默认使用 prefer-online 模式,即优先使用网络数据,若是网络数据请求失败,再去请求缓存数据。

参考

相关文章
相关标签/搜索