共计 2913 个字符,预计需要花费 8 分钟才能阅读完成。
从这篇文章中能取得什么?
- 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 值是怎么生成的?
留一个思考:就是如果没有 lock 文件,是不是就意味着不会从缓存里拿资源了?而是每次都通过网络下载?
npm 依赖管理机制
首先咱们来看一下 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
文章始发与公众号:前端程序喵,欢送大家关注