npm install的原理

当执行 npm install 时,npm会查看 package-lock.json 文件,根据其中列出的 versionresolvedintegrity 字段来确定需要安装的确切包版本。如果本地缓存中已经存在与 resolvedintegrity 匹配的包,npm就会从缓存中提取该包,如果本地缓存中没有找到匹配的包,或者 package-lock.json 文件中的 integrity 校验和缓存中的包不匹配,npm就会从 resolved 指定的 URL 下载包,并将其添加到本地缓存中供未来使用。

.npmrc

  • 项目级.npmrc
    • 在当前项目根目录下 .npmrc
  • 用户级.npmrc
    • 本地磁盘用户文件夹下的 .npmrc
  • 全局的.npmrc
    • AppData下npm文件夹下
  • npm内置的.npmrc
    • 在Nodejs安装文件夹下的npm下 .npmrc
1# 定义npm的registry,即npm的包下载源
2registry=http://registry.npmjs.org/
3
4# 定义npm的代理服务器,用于访问网络
5proxy=http://proxy.example.com:8080/
6
7# 定义npm的https代理服务器,用于访问网络
8https-proxy=http://proxy.example.com:8080/
9
10# 是否在SSL证书验证错误时退出
11strict-ssl=true
12
13# 定义自定义CA证书文件的路径
14cafile=/path/to/cafile.pem
15
16# 自定义请求头中的User-Agent
17user-agent=npm/{npm-version} node/{node-version} {platform}
18
19# 安装包时是否自动保存到package.json的dependencies中
20save=true
21
22# 安装包时是否自动保存到package.json的devDependencies中
23save-dev=true
24
25# 安装包时是否精确保存版本号
26save-exact=true
27
28# 是否在安装时检查依赖的node和npm版本是否符合要求
29engine-strict=true
30
31# 是否在运行脚本时自动将node的路径添加到PATH环境变量中
32scripts-prepend-node-path=true

npm i 在安装依赖时使用的算法称为“npm依赖解析算法”,以下是基本步骤:

  1. 读取项目的 package.json 文件,获取项目的依赖列表。

  2. 对于每一个依赖,npm会查找是否已经安装了满足版本要求的包。

    • 如果已经安装了满足要求的包,npm会使用已经安装的包。
    • 如果没有,npm会从仓库下载一个满足版本要求的包。
  3. 在下载包的过程中,npm会检查包的 package.json 文件,看看包是否还依赖了其他的包。如果有,npm会重复上面的步骤,直到所有的依赖都被解决。

  4. 在解决依赖的过程中,npm会尽可能地重用已经安装的包,以减少需要下载包的数量。这是通过一个叫做“最大化版本满足”的策略来实现的。

  5. 最后,npm会生成一个 node_modules 目录,这个目录中包含了所有下载的包,以及它们的依赖。这个目录的结构是树形的,每个包都是树的节点,依赖的包是子节点。

首先安装的依赖都会存放在根目录的 node_modules,默认采用扁平化的方式安装,并且排序规则.bin第一个然后@系列,再然后按照首字母排序abcd等,并且使用的算法是广度优先遍历,在遍历依赖树时,npm会首先处理项目根目录下的依赖,然后逐层处理每个依赖包的依赖,直到所有依赖都被处理完毕。在处理每个依赖时,npm会检查该依赖的版本号是否符合依赖树中其他依赖的版本要求,如果不符合,则会尝试安装适合的版本

术语解释

扁平化

扁平化只是理想状态下:

安装某个二级模块时,若发现第一层级有相同名称,相同版本的模块,便直接复用那个模块

因为A模块下的C模块被安装到了第一级,这使得B模块能够复用处在同一级下;且名称,版本,均相同的C模块

非理想状态下:

因为B和A所要求的依赖模块不同,(B下要求是v2.0的C,A下要求是v1.0的C )所以B不能像2中那样复用A下的C v1.0模块 所以如果这种情况还是会出现模块冗余的情况,他就会给B继续搞一层node_modules,就是非扁平化了。

广度优先:

最大化版本满足

“最大化版本满足” 是 npm 在解析和安装依赖时采用的一种策略。

这种策略的目标是尽可能的重用已经安装的包,以减少需要下载的包的数量。

具体来说,它的工作原理如下:

当一个项目需要一个特定的包时,npm会查看已经安装的包,看是否有一个版本可以满足项目的需求。如果有多个版本都可以满足需求,npm会选择一个最新的版本。这样如果后来又有其他项目需要这个包,就有更大的可能性可以重用这个已经安装的包。

例如:

假设项目A需要包B的1.0.0版本,项目C需要包B的1.0.1版本。

如果先安装项目A,然后安装项目C,npm会先安装包B的1.0.0版本,然后再安装1.0.1版本。但是如果使用了最大化版本满足的策略,npm会之间安装1.0.1版本,这样就可以满足两个项目的需求,而且只需要下载一次包。

这个策略可以提高安装的速度,同时也可以减少磁盘空间的使用。但是它也可能会导致一些问题,例如版本冲突。因此,npm在实际应用中会结合其他策略,如语义版本控制,来确保依赖的正确性。

details 语义版本控制

语义版本控制,也称为SemVer,是一种版本命名规范。

它规定了版本号的格式以及版本号的递增规则,使得版本号能够传达关于底层代码改动的一些信息。

语义版本号由三部分组成:主版本号、次版本号和修订号,格式为:主版本号.次版本号.修订号 ,例如:1.2.3

  • 主版本号:当你做了不兼容的API修正,需要增加主版本号。
  • 次版本号:当你做了向下兼容的新功能,需要增加次版本号。
  • 修订号:当你做了向下兼容的问题修正,需要增加修订号。

使用语义版本控制,可以清晰地了解每个版本之间的差异,也方便依赖管理。在使用npm等包管理工具时,可以通过语义版本控制来精确地指定依赖包的版本,也可以使用一些通配符来灵活地匹配多个版本。

通配符 含义
~ 匹配当前次版本号下的最大版本,比如 ~1.2.3 会匹配所有 1.2.x 的版本,但是最小版本是 1.2.3 。如果有 1.2.41.2.5 这样的版本,它会优先匹配 1.2.5 这个更高的版本。但是,它不会匹配 1.3.0 这样次版本号变化的版本。
^ 匹配当前主版本号下的最大版本,比如 ^1.2.3 会匹配所有 1.x.x 版本,但是最小版本是 1.2.3。如果有 1.4.21.3.9 这样的版本,它会优先匹配 1.4.2 这个更高的版本。但是,它不会匹配 2.0.0 这样主版本号变化的版本。
* 匹配任何版本
>= 匹配大于等于指定版本的任何版本
<= 匹配小于等于指定版本的任何版本
> 匹配大于指定版本的任何版本
< 匹配小于指定版本的任何版本
"" 匹配任何版本

node_modules下包的排序规则

在 Node.js 的 node_modules 目录下,包的排序规则并不是有 Node.js 决定的,而是由你的文件系统决定的。不同的文件系统可能有不同的排序规则。

在大多数情况下,文件系统通常会按照字母顺序进行排序。在ASCII编码中,特殊字符(如 .@ )的编码值都比字母和数字小,因此它们通常排在前面。

  • 首先安装的依赖都会存放在根目录的 node_modules ,默认采用扁平化的方式安装,并且排序按照先 .@ ,再然后按照首字母排序,并且使用的算法是广度优先遍历,在遍历依赖树时,npm会首先处理项目根目录下的依赖,然后逐层处理每个依赖包的依赖,直到所有依赖都被处理完毕。在处理依每个赖时,npm会检查该依赖的版本号是否符合当前依赖树中其他依赖的版本要求,如果不符合,则会尝试安装适合的版本。