本文首发于政采云前端团队博客: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 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 会带来一个新的问题:因为在执行 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.json
中dependencies
的依赖项相同 -
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.0
和 React-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/)
看完两件事
招贤纳士
ZooTeam@cai-inc.com

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