面试官: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放心,小编情人节还在写文章,根本不知道对象为何物

相关推荐
张3蜂26 分钟前
Python 四大 Web 框架对比解析:FastAPI、Django、Flask 与 Tornado
前端·python·fastapi
南风知我意95727 分钟前
【前端面试5】手写Function原型方法
前端·面试·职场和发展
qq_124987075327 分钟前
基于Java Web的城市花园小区维修管理系统的设计与实现(源码+论文+部署+安装)
java·开发语言·前端·spring boot·spring·毕业设计·计算机毕业设计
小安驾到1 小时前
【前端的坑】vxe-grid表格tooltip提示框不显示bug
前端·vue.js
去码头整点薯条981 小时前
python第五次作业
linux·前端·python
沐墨染1 小时前
Vue实战:自动化研判报告组件的设计与实现
前端·javascript·信息可视化·数据分析·自动化·vue
毕设源码-朱学姐1 小时前
【开题答辩全过程】以 基于Node.js的书籍分享平台设计与实现为例,包含答辩的问题和答案
node.js
局外人LZ2 小时前
Uniapp脚手架项目搭建,uniapp+vue3+uView pro+vite+pinia+sass
前端·uni-app·sass
爱上妖精的尾巴2 小时前
8-5 WPS JS宏 match、search、replace、split支持正则表达式的字符串函数
开发语言·前端·javascript·wps·jsa
为什么不问问神奇的海螺呢丶3 小时前
n9e categraf redis监控配置
前端·redis·bootstrap