npm 依赖管理中被忽略的那些细节

👆   这是第  66  篇 不掺水的原创 ,想要了解更多 ,请戳上方蓝色字体: 政采云前端团队  关注咱们吧~

本文首发于政采云前端团队博客:npm 依赖管理中被忽略的那些细节html

https://www.zoo.team/article/npm-details


前言

提起 npm,你们第一个想到的应该就是 npm install 了,可是 npm install 以后生成的 node_modules 你们有观察过吗?package-lock.json 文件的做用你们知道吗?除了 dependencies 和 devDependencies,其余的依赖有什么做用呢?接下来,本文将针对 npm 中的你可能忽略的细节和你们分享一些经验。前端

npm 安装机制

A 和 B 同时依赖 C,C 这个包会被安装在哪里呢?C 的版本相同和版本不一样时安装会有什么差别呢?package.json 中包的先后顺序对于安装时有什么影响吗?这些问题平时你们可能没有注意过,今天咱们就来一块儿研究一下吧。node

A 和 B 同时依赖 C,这个包会被安装在哪里呢?

假若有 A 和 B 两个包,两个包都依赖 C 这个包,npm 2 会依次递归安装 A 和 B 两个包及其子依赖包到 node_modules 中。执行完毕后,咱们会看到 ./node_modules 这层目录只含有这两个子目录:react

node_modules/
├─┬ A
│ ├── C
├─┬ B
│ └── C

若是使用 npm 3 来进行安装的话,./node_modules 下的目录将会包含三个子目录:ios

node_modules/
├─┬ A
├─┬ B
├─┬ C

为何会出现这样的区别呢?这就要从 npm 的工做方式提及了:git

npm 2 和 npm 3 模块安装机制的差别

虽然目前最新的 npm 版本是 npm 6,但 npm 2 到 npm 3 的版本变动中实现了目录打平,与其余版本相比差异较大。所以,让咱们具体看下这两个版本的差别。github

npm 2 在安装依赖包时,采用简单的递归安装方法。执行 npm install 后,npm 根据 dependencies 和 devDependencies 属性中指定的包来肯定第一层依赖,npm 2 会根据第一层依赖的子依赖,递归安装各个包到子依赖的 node_modules 中,直到子依赖再也不依赖其余模块。执行完毕后,咱们会看到 ./node_modules 这层目录中包含有咱们 package.json 文件中全部的依赖包,而这些依赖包的子依赖包都安装在了本身的 node_modules 中 ,造成相似于下面的依赖树:

这样的目录有较为明显的好处:web

1)层级结构很是明显,能够清楚的在第一层的 node_modules 中看到咱们安装的全部包的子目录;npm

2)在已知本身所需包的名字以及版本号时,能够复制粘贴相应的文件到 node_modules 中,而后手动更改 package.json 中的配置;json

3)若是想要删除某个包,只须要简单的删除 package.json 文件中相应的某一行,而后删除 node_modules 中该包的目录;

可是这样的层级结构也有较为明显的缺陷,当个人 A,B,C 三个包中有相同的依赖 D 时,执行 npm install 后,D 会被重复下载三次,而随着咱们的项目愈来愈复杂,node_modules 中的依赖树也会愈来愈复杂,像 D 这样的包也会愈来愈多,形成了大量的冗余;在 windows 系统中,甚至会由于目录的层级太深致使文件的路径过长,触发文件路径不能超过 280 个字符的错误;

为了解决以上问题,npm 3 的 node_modules 目录改为了更为扁平状的层级结构,尽可能把依赖以及依赖的依赖平铺在 node_modules 文件夹下共享使用。

npm  3 对于同一依赖的不一样版本会怎么处理呢?

npm 3 会遍历全部的节点,逐个将模块放在 node_modules 的第一层,当发现有重复模块时,则丢弃, 若是遇到某些依赖版本不兼容的问题,则继续采用 npm 2 的处理方式,前面的放在 node_modules 目录中,后面的放在依赖树中。举个🌰:A,B,依赖 D(v 0.0.1),C 依赖 D(v 0.0.2):

可是 npm 3 会带来一个新的问题:因为在执行 npm install 的时候,按照 package.json 里依赖的顺序依次解析,上图若是 C 的顺序在 A,B 的前边,node_modules 树则会改变,会出现下边的状况:

因而可知,npm 3 并未彻底解决冗余的问题,甚至还会带来新的问题。

为何会出现 package-lock.json 呢?

为何会有 package-lock.json 文件呢?这个咱们就要先从 package.json 文件提及了。

package.json 的不足之处

npm install 执行后,会生成一个 node_modules 树,在理想状况下, 但愿对于同一个 package.json 老是生成彻底相同 node_modules 树。在某些状况下,确实如此。但在多数状况下,npm 没法作到这一点。有如下两个缘由:

1)某些依赖项自上次安装以来,可能已发布了新版本 。好比:A 包在团队中第一我的安装的时候是 1.0.5 版本,package.json 中的配置项为 A: '^1.0.5';团队中第二我的把代码拉下来的时候,A 包的版本已经升级成了 1.0.8,根据 package.json 中的 semver-range version 规范,此时第二我的 npm install 后 A 的版本为 1.0.8;可能会形成由于依赖版本不一样而致使的 bug;

2)针对 1)中的问题,可能有的小伙伴会是把 A 的版本号固定为 A: '1.0.5' 不就能够了吗?可是这样的作法其实并无解决问题, 好比 A 的某个依赖在第一我的下载的时候是 2.1.3 版本,可是第二我的下载的时候已经升级到了 2.2.5 版本,此时生成的 node_modules 树依旧不彻底相同 ,固定版本只是固定来自身的版本,依赖的版本没法固定。

针对 package.json 不足的解决方法

为了解决上述问题以及 npm 3 的问题,在 npm 5.0 版本后,npm install 后都会自动生成一个 package-lock.json 文件 ,当包中有 package-lock.json 文件时,npm install 执行时,若是 package.json 和 package-lock.json 中的版本兼容,会根据 package-lock.json 中的版本下载;若是不兼容,将会根据 package.json 的版本,更新 package-lock.json 中的版本,已保证 package-lock.json 中的版本兼容 package.json。

package-lock.json 文件的结构

package-lock.json 文件中的 name、version 与 package.json 中的 name、version 同样,描述了当前包的名字和版本,dependencies 是一个对象,该对象和 node_modules 中的包结构一一对应,对象的 key 为包的名称,值为包的一些描述信息, 根据 package-lock-json官方文档 (https://docs.npmjs.com/configuring-npm/package-lock-json.html#requires),主要的结构以下:

  • version :包版本,即这个包当前安装在 node_modules 中的版本
  • resolved :包具体的安装来源
  • integrity :包 hash 值,验证已安装的软件包是否被改动过、是否已失效
  • requires :对应子依赖的依赖,与子依赖的 package.jsondependencies 的依赖项相同
  • dependencies :结构和外层的 dependencies 结构相同,存储安装在子依赖 node_modules 中的依赖包

须要注意的是,并非全部的子依赖都有 dependencies 属性,只有子依赖的依赖和当前已安装在根目录的 node_modules 中的依赖冲突以后,才会有这个属性。

package-lock.json 文件的做用

  • 在团队开发中,确保每一个团队成员安装的依赖版本是一致的,肯定一棵惟一的 node_modules 树;
  • node_modules 目录自己是不会被提交到代码库的,可是 package-lock.json 能够提交到代码库,若是开发人员想要回溯到某一天的目录状态,只须要把 package.json 和 package-lock.json 这两个文件回退到那一天便可。
  • 因为 package-lock.json 和 node_modules 中的依赖嵌套彻底一致,能够更加清楚的了解树的结构及其变化。
  • 在安装时,npm 会比较 node_modules 已有的包,和 package-lock.json 进行比较,若是重复的话,就跳过安装 ,从而优化了安装的过程。

依赖的区别与使用场景

npm 目前支持如下几类依赖包管理包括

  • dependencies
  • devDependencies
  • optionalDependencies 可选择的依赖包
  • peerDependencies 同等依赖
  • bundledDependencies 捆绑依赖包

下面咱们来看一下这几种依赖的区别以及各自的应用场景:

dependencies

dependencies 是不管在开发环境仍是在生产环境都必须使用的依赖,是咱们最经常使用的依赖包管理对象,例如 React,Loadsh,Axios 等,经过 npm install XXX 下载的包都会默认安装在 dependencies 对象中,也可使用  npm install XXX --save 下载  dependencies 中的包;

devDependencies

devDependencies 是指能够在开发环境使用的依赖,例如 eslint,debug 等,经过 npm install packageName --save-dev 下载的包都会在 devDependencies 对象中;

dependencies 和 devDependencies 最大的区别是在打包运行时,执行 npm install 时默认会把全部依赖所有安装,可是若是使用 npm install --production 时就只会安装 dependencies 中的依赖,若是是 node 服务项目,就能够采用这样的方式用于服务运行时安装和打包,减小包大小。

optionalDependencies

optionalDependencies 指的是能够选择的依赖,当你但愿某些依赖即便下载失败或者没有找到时,项目依然能够正常运行或者 npm 继续运行的时,就能够把这些依赖放在 optionalDependencies 对象中,可是 optionalDependencies 会覆盖 dependencies 中的同名依赖包,因此不要把一个包同时写进两个对象中。

optionalDependencies 就像是咱们的代码的一种保护机制同样,若是包存在的话就走存在的逻辑,不存在的就走不存在的逻辑。

try { 
  var axios = require('axios'
  var fooVersion = require('axios/package.json').version 
catch (er) { 
  foo = null 

// .. then later in your program .. 
if (foo) { 
  foo.doFooThings() 

peerDependencies

peerDependencies 用于指定你当前的插件兼容的宿主必需要安装的包的版本,这个是什么意思呢?举个例子🌰:咱们经常使用的 react 组件库 ant-design@3.x 的 package.json (https://github.com/ant-design/ant-design/blob/master/package.json#L37) 中的配置以下:

"peerDependencies": { 
  "react"">=16.9.0"
  "react-dom"">=16.9.0" 
 }, 

假设咱们建立了一个名为 project 的项目,在此项目中咱们要使用 ant-design@3.x 这个插件,此时咱们的项目就必须先安装 React >= 16.9.0React-dom >= 16.9.0 的版本。

在 npm 2 中,当咱们下载 ant-design@3.x 时,peerDependencies 中指定的依赖会随着 ant-design@3.x 一块儿被强制安装,因此咱们不须要在宿主项目的 package.json 文件中指定 peerDependencies 中的依赖,可是在 npm 3 中,不会再强制安装 peerDependencies 中所指定的包,而是经过警告的方式来提示咱们,此时就须要手动在 package.json 文件中手动添加依赖;

bundledDependencies

这个依赖项也能够记为 bundleDependencies,与其余几种依赖项不一样,他不是一个键值对的对象,而是一个数组,数组里是包名的字符串,例如:


  "name""project"
  "version""1.0.0"
  "bundleDependencies": [ 
    "axios",  
    "lodash" 
  ] 

当使用 npm pack 的方式来打包时,上述的例子会生成一个 project-1.0.0.tgz 的文件,在使用了 bundledDependencies 后,打包时会把 Axios 和 Lodash 这两个依赖一块儿放入包中,以后有人使用 npm install project-1.0.0.tgz 下载包时,Axios 和 Lodash 这两个依赖也会被安装。须要注意的是安装以后 Axios 和 Lodash 这两个包的信息在 dependencies 中,而且不包括版本信息。

"bundleDependencies": [ 
    "axios"
    "lodash" 
  ], 
  "dependencies": { 
    "axios""*"
    "lodash""*" 
  }, 

若是咱们使用常规的 npm publish 来发布的话,这个属性是不会生效的,因此平常状况中使用的较少。

总结

本文介绍的是 npm 2,npm 3,package-lock.json 以及几种依赖的区别和使用场景,但愿可以让你们对 npm 的了解更加多一点,有什么不清楚的地方或者不足之处欢迎你们在评论区留言。

参考文献

package.json官方文档 (https://docs.npmjs.com/files/package.json#peerdependencies)

package-lock-json官方文档 (https://docs.npmjs.com/configuring-npm/package-lock-json.html#requires)

npm文档总结 (https://juejin.im/post/6844903582337237006#heading-0)

npm-pack (https://www.npmjs.cn/cli/pack/)

看完两件事

若是你以为这篇内容对你挺有启发,我想邀请你帮我两件小事
1.点个「 在看」,让更多人也能看到这篇内容(点了 在看 」,bug -1 😊
2.关注公众号「 政采云前端团队」,持续为你推送精选好文

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 50 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在平常的业务对接以外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推进并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5 年工做时间 3 年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手推进一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com

本文分享自微信公众号 - 政采云前端团队(Zoo-Team)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索