简单探究npm相关机制
从这篇文章中能获得什么?
- npm的安装机制,即在执行install命令时都做了什么事?
- npm的缓存策略,这里做了简单介绍,没深入。
- npm的依赖管理,即我们每次安装的依赖都是怎么在node_modules进行管理的?
- lock文件究竟有什么用?我们到底需要吗?
如果不清楚的话可以沉下心看8分钟。
npm依赖安装机制 首先我们来了解一下
npm
依赖安装的设计哲学:npm
安装依赖的时候会优先安装在当前项目的目录下,保持各个项目依赖的独立性。优点: 可以减少开发者的心智负担,方便维护各个项目下的不同依赖。当然像
缺点:A项目和B项目都依赖一个包,这时候就会产生依赖重复安装,造成电脑内存资源的浪费。
webpack
、creat-react-app
这些依赖也可以安装在全局,这样做的目的是为了方便设置环境变量path
,让我们可以在任何地方调用对应的命令。了解了这些以后我们来探究一下npm依赖安装的核心机制。
?
文章图片
通过上图我们可以看到npm安装依赖的整个脉络,接下来我们逐个分析
?
当我们执行
npm install
命令时,会首先去查找对应的配置文件,来告诉npm
要以什么样的规则去下载文件。config
文件有多个,但是遵循一个优先级:项目级的.npmrc > 用户级的.npmrc > 全局的.npmrc > npm内置的.npmrc文件
我们可以通过
npm config ls -l
来查看当前的配置
?确定了
config
之后,就会看当前项目下有没有package-lock.json
文件。?
如果有
package-lock.json
文件,那么会对比pacakge.json
和lock
文件:- 如果依赖版本规范兼容的话,会按照lock文件中的信息进行资源的下载
- 如果依赖版本规范不兼容的话,不同版本会有不同的处理(详情看上图)
其实大部分情况下两个版本规范都是兼容的,所以大部分都是按照lock文件去安装依赖。
除非你手动更改依赖版本号,可能就会出现不兼容的情况。
总结来说就是:
规范兼容按照**lock**
下载依赖。
不兼容按照**package.json**
下载依赖,并且最后更新lock文件。
这里有个问题就是怎么来判断两个依赖的版本是否兼容?如果没有
这里需要知道一个知识点, 什么是semver? 官方文档
package-lock.json
文件,那么就会按照package.json
文件去进行依赖树的构建。?
构建依赖树的时候,遵循扁平化的原则,也就是会优先将依赖放在
node_modules
的根目录下。后面在安装其他依赖的时候,会判断该依赖是否已经存在,如果存在的话,就会比较两个依赖的版本规范是否兼容,如果兼容则跳过该依赖的安装(忽略该依赖,因为已经存在),如果不兼容的话,会将该版本的依赖单独放在其父级的node_modules
中然后在获取依赖资源的时候会优先考虑缓存,这样可以加快依赖的下载速度。
?
npm依赖缓存策略 我们可以通过下面的命令来获取到缓存地址,我们可以在对应的__caches文件夹里查看相关文件。
npm config get cache
那在我们每次install的时候都是怎么进行缓存的读取的呢?
在我们
install
的时候,npm
会先将资源下载到缓存当中,然后才会解压的项目对应的node_modules
中。之后在每次安装资源的时候都会按照
package-lock.json
文件中的version
、integrity
、name
信息来生成一个key
值,这个key
值可以命中index-v5
中的缓存资源的信息。如果命中缓存了,就会找到对应的tar
包,解压到项目中的node_modules
中。?
这里其实可以探究一下这个key值是怎么生成的?npm依赖管理机制 首先我们来看一下
留一个思考:就是如果没有lock文件,是不是就意味着不会从缓存里拿资源了?而是每次都通过网络下载?
v2
版本的npm
依赖管理是怎么做的。当一个项目中有
A
和B
两个依赖的时候,会在node_modules
中依次安装这两个依赖。并且这两个依赖都有一个共同的依赖项
C
,那么在v2
中就会在A
和B
文件夹的node_modules
中各自安装C
。文章图片
这样就会造成一个问题,那就是依赖地狱。
- 依赖地狱会造成内存资源的浪费
- 而且重复安装依赖,会造成安装进度缓慢
npm
从v3
开始,就采取了扁平化的结构(其实当前npm
很多机制都是借鉴了yarn
的设计)。在扁平化的结构下,npm是怎么进行管理依赖的呢?我们接下来一步步屡清楚。
首先依赖
A
有一个依赖项C
版本号为v1
,那么在安装依赖的时候就是这样的:文章图片
然后在安装依赖
B
的时候,他也依赖v1
版本的C
,那么他就会从node_modules根目录中寻找该依赖,此时的依赖结构如下:文章图片
那么如果依赖
B
,它依赖的C
版本是V2
,那么结构就会发生变化,B
会在自己目录下的node_modules
安装对应v2
版本的C
:文章图片
根据上面的这种情况,继续往下看如果此时我们需要升级依赖
A
为v2
,该版本的A
,不再依赖v1
版本的C
,而是依赖V2
版本的C
。那么此时npm
会怎么做呢?- 删除
A
- 因为
v1
版本的C
已经没有包依赖了,所以也会删除 - 安装
v2
版本的A
? - 因为此时根目录下没有
v2
的C
,v2
版本的C
会安装在根目录下
文章图片
到这里可能有个问题,为啥都依赖从这里就可以看出,在v2
版本的C
,依赖B
还要在自己的目录下单独安装v2
的C
?
因为B
依赖是后安装的,安装B
的时候已经在根目录里存在了v1
版本的C
的包,所以v2
版本就安装在了它自身的目录。
npm
里依赖的安装顺序对依赖结构的影响会非常大,可能会影响node_modules
的文件大小。那么我们有办法让它完全扁平化吗?
当然是可以的,
npm
提供了一个命令,可以帮助我们处理这些重复文件,也可以理解为帮我们扁平化npm dedupe
执行之后结构就变成了更优雅的形式:
文章图片
这里值得一提的是,这里给大家看一个实际案例,我初始化了一个新的项目,这个项目只安装了一个依赖yarn
在安装依赖的时候会自动执行dedupe
命令,帮我们自动拍平。
find-css-import
(这个包是我写的,所以比较熟悉),这个包是只有一个依赖strip-comments v2.0
。当我们首次安装这个依赖的时候,项目中的
node_modules
文件夹就会一次出现这两个文件。这种现象也是符合上面我们分析的结果的。
然后我们手动的去改一下这个目录结构,把
strip-comments
文件夹放到find-css-import
的node_modules
中去,然后在执行一下dedupe
来模拟一下扁平化的这个过程。手动修改后的的目录结构是这样的:
文章图片
然后我们去执行一下
dedupe
可以看到它成功的将文件结构给拍平了~
总结 这篇文章其实是一篇笔记,对自己这两天看到的知识进行一个归纳总结。
其实说起来想包管理工具虽然日常我们经常使用,但是有很多细节都是忽略的,也是希望通过这篇文章来捡起来一些东西。
参考
- LucasHC(侯策)老师 - 前端基础建设与架构30讲
- 山月老师 - 什么是semver
文章始发与公众号:前端程序喵,欢迎大家关注
【简单探究npm相关机制】
文章图片
推荐阅读
- 科学养胃,别被忽悠,其实真的很简单
- opencv|opencv C++模板匹配的简单实现
- 松软可口易消化,无需烤箱超简单,新手麻麻也能轻松成功~
- 简单心理2019春A期+32+张荣
- 《算法》-图[有向图]
- android防止连续点击的简单实现(kotlin)
- 机器学习一些简单笔记
- Android超简单实现沉浸式状态栏
- 用npm发布一个包的教程并编写一个vue的插件发布
- v-charts简单使用