我们想让前端代码跑动起来,要么开发环境运行或者打包后运行到服务器,都要经过nodejs相关的操作。而nodejs的包管理工具npm是这些命令的管理中心,而npm install是npm的一个核心的命令,是项目相关依赖安装的一个命令。
我们先用一个表格看看npm install 的大致过程:
| 总结 | 详细 |
|---|---|
| 解析 package.json 文件 | 当你在项目目录下运行 npm install 时,npm 首先会读取项目根目录下的 package.json 文件。这个文件定义了项目的元数据,包括项目名称、版本、依赖项等。 |
| 创建或更新 node_modules 目录 | npm install 命令会在项目根目录下创建一个 node_modules 目录(如果之前不存在的话),或者更新这个目录中的内容。这个目录用于存放所有安装的 npm 包。 |
| 解析依赖树 | npm 会解析 package.json 文件中的 dependencies 和 devDependencies(开发依赖)字段,构建出项目的依赖树。这个依赖树包括了直接依赖以及所有间接依赖。 |
| 下载和安装包 | 根据解析出的依赖树,npm 会从 npm 仓库(通常是 https://registry.npmjs.org/)下载相应的包。对于每个包,npm 会检查本地缓存(通常位于 ~/.npm),如果缓存中有对应的包版本,则直接使用缓存中的版本,否则会下载最新版本。 |
| 安装钩子执行 | 在安装完所有依赖后,npm 会执行每个包的 postinstall 脚本(如果有的话)。这些脚本通常用于执行额外的安装步骤,比如编译 C++ 扩展等。 |
| 生成 package-lock.json 文件 | 为了提高可重复性和安全性,npm 会生成或更新 package-lock.json 文件。这个文件记录了安装时确切的包版本号,确保团队成员使用相同版本的依赖项。 |
| 完成安装 | 所有依赖安装完毕后,npm install 命令结束。此时,你的项目可以使用这些依赖包进行开发或运行。 |
其实上述是一个没有缓存的install过程。其实,我们npm install可以用下列图一探究竟的:

这是横放的图:

1、npm install 执行之后,首先,检查并获取 npm 配置
2、检查项目中是否有 package-lock.json 文件
若有: 检查 package-lock.json 和 package.json 中声明的依赖是否一致:一致就直接使用package-lock.json 中的信息,从缓存或网络资源中加载依赖;不一致,则依据npm的版本进行处理
若无:则根据 package.json 递归构建依赖树,按照构建好的依赖树下载完整的依赖资源,在下载时就会检查是否存在相关资源缓存:存在缓存,就会将缓存内容解压到node_modules 中;不存在缓存,就从npm远程仓库下载包,并会检验包的完整性,随后添加到缓存,解压到node_modules 中生成package-lock.json。
3、最后生成 package-lock.json
安装依赖时,使用的算法是广度优先遍历,在遍历依赖树时,npm会首先处理项目根目录下的依赖,然后逐层处理每个依赖包的依赖,直到所有依赖都被处理完毕。在处理每个依赖时,npm会检查该依赖的版本号是否符合依赖树中其他依赖的版本要求,如果不符合,则会尝试安装适合的版本。
这是某人的npmrc配置:
cpp
registry=http://registry.npmjs.org/
# 定义npm的registry,即npm的包下载源
proxy=http://proxy.example.com:8080/
# 定义npm的代理服务器,用于访问网络
https-proxy=http://proxy.example.com:8080/
# 定义npm的https代理服务器,用于访问网络
strict-ssl=true
# 是否在SSL证书验证错误时退出
cafile=/path/to/cafile.pem
# 定义自定义CA证书文件的路径
user-agent=npm/{npm-version} node/{node-version} {platform}
# 自定义请求头中的User-Agent
save=true
# 安装包时是否自动保存到package.json的dependencies中
save-dev=true
# 安装包时是否自动保存到package.json的devDependencies中
save-exact=true
# 安装包时是否精确保存版本号
engine-strict=true
# 是否在安装时检查依赖的node和npm版本是否符合要求
scripts-prepend-node-path=true
# 是否在运行脚本时自动将node的路径添加到PATH环境变量中
package-lock.json 的作用
该文件是用来锁定版本记录依赖树的详细信息用的。
version: 包版本这个包当前安装在 node_modules 中的版本
resolved: 包具体的安装来源,通常是npm,也有是私有源
integrity: 包 hash 值,来判断安装的依赖包是否被改动过、是否已失效源
requires: 对应子依赖的依赖,与子依赖的 package.json 中 dependencies的依赖项相同
dependencies: 存储安装在子依赖 node_modules 中的依赖包。
我们打开package.lock.json文件看看内容:

package-lock.json 帮我们做了缓存,他会通过name + version + integrity信息生成一个唯一的key,这个key能找到对应的index-v5 下的缓存记录,也就是npm cache 文件夹下的。

如果发现有缓存记录,就会找到tar包的hash值,然后将对应的二进制文件解压到node_modules。

node_modules 中模块目录结构
npm3.x之前是嵌套结构,那时候如果我们的项目中依赖了公共库 A 和公共库 B,同时公共库 A 也依赖了公共库 B,那公共库 B 会被多次安装 结构如图

node_modules 的结构和 package.json 结构一一对应,层级结构明显,并且保证了每次安装目录结构都是相同的。但是一个项目中引用的模块非常多,嵌套层级很深这时候就出现:在不同层级的依赖中,可能引用了同一个模块,这样这个模块就存在多个地方。显然不符合编码简洁的思路。
扁平结构
为了解决以上问题,npm3版本进行了改造。把嵌套结构改为扁平结构: 安装模块时,不管其是直接依赖还是子依赖的依赖,优先将其安装在 node_modules 根目录。 还是上面的依赖结构,我们在执行 npm install 后将得到下面的目录结构


寻找模块的依赖的方式就成了如下的流程:
- 在当前模块路径下搜索
- 在当前模块 node_modules 路径下搜素
- 在上级模块的 node_modules 路径下搜索
- 直到搜索到全局路径中的 node_modules
我们项目中安装依赖的时候需要注意:
◆ 应对node_modules庞大性
面对node_modules的庞大性,我们需要了解扁平化是npm安装的常态,但版本冲突可能导致嵌套现象。为了查看包的实际位置和版本,我们可以使用npm lspackage_name>命令,或者借助工具如disk-usage来分析存储占用。
◆ 解决安装缓慢问题
推荐使用国内镜像源,通过修改.npmrc文件中的registry设置,确保网络连接的稳定性。在持续集成(CI)环境中,使用npm ci命令通常能获得更快的安装速度。
◆ 保持环境一致性
提交package-lock.json文件,以确保团队成员在相同环境下使用一致的依赖版本。推荐使用npm ci进行确定性安装,确保每次部署都能获得一致的结果。
◆ 手动处理依赖冲突
当需要进行手动干预时,可以通过使用npm updatepackage>命令或在package.json中添加overrides(npm v8+)或resolutions(Yarn字段)来强制指定版本,但需谨慎操作。
参考文章:
https://juejin.cn/post/7156041372678488072
https://blog.csdn.net/weixin_40629244/article/details/139582982
https://baijiahao.baidu.com/s?id=1840449964886370148\&wfr=spider\&for=pc