面试官:npm install 之后发生了什么 我:什么❓

小白同学终于迎来了期待已久的二面,然而滴滴面试官的这一问题却让他有些"破防"了。自诩略懂前端工程化的他,听到面试官问"npm install 之后究竟发生了什么?"时,陷入了沉默。

小白同学:"...什么?"

面试官微微一笑,并给出了最后的轻语 😭:"那今天的面试就先到这里吧,好吧?" 回到家中,小白同学决定好好研究这个问题,想弄明白"npm install 后,究竟发生了什么?

他开始深入思考,阅读文档,最终发现了其中的奥秘------

什么是npm?

npm(node package manager),是随同Node.js一起安装的第三方包管理器。通过npm,我们可以安装、共享、分发代码,管理项目的依赖关系。

嵌套结构

在npm的早期版本中,npm以递归的方式去处理依赖,每个依赖包都会在自己的 node_modules 目录中安装其子依赖,直到没有子依赖为止。

举个栗子,我们项目安装axios依赖,axios自身需要三个依赖:

而其中的form-data又存在三个依赖,这里就不一一列举了,当执行npm install命令后,得到的node_modules中的模块目录结构:

css 复制代码
my-project
└── node_modules
    ├── axios
    │   ├── follow-redirects      
    │   ├── form-data              
    │   │   ├── asynckit          
    │   │   ├── combined-stream    
    │   │   │   └── delayed-stream  
    │   │   └── mime-types          
    │   │       └── mime-db        
    │   └── proxy-from-env   

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

但也存在不容忽视的缺点:如果项目一旦变大,依赖变多,node_modules将变得非常庞大,特别是不同层级的依赖如果共用一个依赖,还会造成不必要的冗余,而且整个的嵌套层级也会非常深。

扁平结构

为了解决以上的问题,npm3.x开始将嵌套结构改为了扁平结构,当安装模块时,不管是直接依赖还是子依赖,优先将其安装在node_modules根目录

这样就是一个扁平结构,当安装到相同的时候,判断已安装的是否符合新的版本范围,如果符合就跳过,不符合就继续安装

配置文件

package.json

一般来说,任何使用Node.js的项目都需要有一个package.json文件,该文件中包括项目名称、版本、描述和所依赖的包

package-lock.json

为了解决 npm install 的不确定性问题,在 npm 5.x 版本新增了 package-lock.json 文件,而安装方式还沿用了 npm 3.x 的扁平化的方式。它描述了生成的确切树,以便后续安装能够生成相同的树,而不管中间依赖更新如何。

.npmrc

控制 npm 的行为,如注册表、代理、缓存路径等。

ini 复制代码
# 包下载源
registry=https://registry.npmmirror.com

# 设置作用域包的私有仓库(如公司内部包)
@mycompany:registry=https://npm.mycompany.com

# 设置缓存目录路径(默认 ~/.npm)
cache=~/.custom-npm-cache

# 设置 HTTP 代理(根据实际情况替换)
proxy=http://127.0.0.1:8080

.npmrc 文件的优先级为:项目级 > 用户级 > 全局级 > 内置级。

npm缓存

在执行 npm installnpm update 命令下载依赖后,除了将依赖包安装在 node_modules 目录下外,还会在本地的缓存目录缓存一份。我们可以通过以下命令获取缓存位置:

arduino 复制代码
npm config get cache

打开目录有以下文件夹

content-v2存放的是依赖实际的内容,而index-v5则是存放依赖的索引信息

依赖完整性

在下载依赖包之前,我们一般就能拿到 npm 对该依赖包计算的 hash 值,例如我们执行 npm info 命令,紧跟 tarball(下载链接) 的就是 shasum(hash)

在下载依赖包之前,npm 会获取其 shasum 哈希值。下载完成后,npm 会在本地重新计算哈希值,并与远程的哈希值对比。如果两者一致,则依赖包完整;否则,npm 会重新下载。

下载包

如果检查到本地缓存中不存在对应的依赖包,便会通过发送网络请求去下载包。具体是通过package-lock.json文件中的resolved字段,当我们尝试通过该字段中的值从浏览器中输入,发现会直接给我们下载了一个文件,例如,我们使用 axios 中的 resolved 中的值,具体值是:

ruby 复制代码
https://registry.npmjs.org/axios/-/axios-1.3.1.tgz

整体流程

  1. 首先,npm install 需要检查是否有附加的命令参数,如 --save--save-dev,以决定依赖的类型(例如:生产依赖或开发依赖)。如果没有指定,则之后会安装 package.json 中列出的所有依赖。
  1. 接着,npm install 会按优先级查找配置文件:项目级 .npmrc > 用户级 .npmrc > 全局级 .npmrc > npm 内置 .npmrc,并根据配置调整安装行为。
  1. 如果项目定义了 preinstall 钩子(例如:npm run preinstall),它会在依赖安装前被执行。可以在此步骤进行一些初始化操作,如检查版本、清理缓存等。
  1. 然后检查是否有lock文件,有的话会检查package.json中的依赖版本是否和package-lock.json中的依赖有冲突。如果没有冲突,直接在缓存中查找包信息。
    如果没有lock文件,会先从npm远程仓库去获取包信息,之后根据package.json构建依赖树,具体过程:
  • 构建依赖树时,不管其是直接依赖还是子依赖的依赖,优先将其放置在 node_modules 根目录。
  • 当遇到相同模块时,判断已放置在依赖树的模块版本是否符合新模块的版本范围,如果符合则跳过,不符合则在当前模块的 node_modules 下放置该模块。
  1. 之后再在缓存中依次查找依赖树的每个包:
  • 不存在缓存:从npm远程仓库下载包,检验包的完整性,检验不通过就重新下载,检验通过会将下载的包复制到npm缓存目录并按照扁平化的依赖结构解压到node-modules中
  • 存在依赖:将缓存按照扁平化的依赖结构解压到node-modules中
  1. 生成lock文件

参考:

juejin.cn/post/719581...

juejin.cn/post/684490...

PS:请JY放心,小编情人节还在写文章,根本不知道对象为何物

相关推荐
passerby606141 分钟前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte3 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc