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

相关推荐
大胖丫几秒前
vue 学习-vite api.js
开发语言·前端·javascript
孙桂月2 分钟前
ES6相关操作(2)
前端·javascript·es6
陈浩源同学2 分钟前
学习 TypeScript 栈和队列数据结构
前端·算法
我这一生如履薄冰~4 分钟前
简单封装一个websocket构造函数
前端·javascript·websocket
fangcaojushi4 分钟前
解决webpack5.54打包图片及图标的问题
前端·vue.js
海盗强4 分钟前
Webpack打包优化
前端·webpack·node.js
星之卡比*6 分钟前
前端面试题---vite和webpack的区别
前端·面试
^^为欢几何^^11 分钟前
npm、pnpm和yarn有什么区别
前端·npm·node.js
AC-PEACE33 分钟前
Vue 中 MVVM、MVC 和 MVP 模式的区别
前端·vue.js·mvc
播播资源36 分钟前
ChatGPT付费创作系统V3.1.3独立版 WEB端+H5端+小程序端 (DeepSeek高级通道+推理输出格式)安装教程
前端·ai·chatgpt·ai作画·小程序·deepseek·deepseek-v3