共计 6073 个字符,预计需要花费 16 分钟才能阅读完成。
第 66 篇原创好文~
本文首发于政采云前端团队博客:npm 中的依赖治理
前言
提起 npm,大家第一个想到的应该就是 npm install 了,然而 npm install 之后生成的 node_modules 大家有察看过吗?package-lock.json 文件的作用大家晓得吗?除了 dependencies 和 devDependencies,其余的依赖有什么作用呢?接下来,本文将针对 npm 中的你可能疏忽的细节和大家分享一些教训。
npm 装置机制
A 和 B 同时依赖 C,C 这个包会被装置在哪里呢?C 的版本雷同和版本不同时装置会有什么差别呢?package.json 中包的前后程序对于装置时有什么影响吗?这些问题平时大家可能没有留神过,明天咱们就来一起钻研一下吧。
A 和 B 同时依赖 C,这个包会被装置在哪里呢?
如果有 A 和 B 两个包,两个包都依赖 C 这个包,npm 2 会顺次递归装置 A 和 B 两个包及其子依赖包到 node_modules 中。执行结束后,咱们会看到 ./node_modules
这层目录只含有这两个子目录:
node_modules/
├─┬ A
│ ├── C
├─┬ B
│ └── C
如果应用 npm 3 来进行装置的话,./node_modules
下的目录将会蕴含三个子目录:
node_modules/
├─┬ A
├─┬ B
├─┬ C
为什么会呈现这样的区别呢?这就要从 npm 的工作形式说起了:
npm 2 和 npm 3 模块装置机制的差别
尽管目前最新的 npm 版本是 npm 6,但 npm 2 到 npm 3 的版本变更中实现了目录打平,与其余版本相比差异较大。因而,让咱们具体看下这两个版本的差别。
npm 2 在装置依赖包时,采纳简略的递归装置办法。执行 npm install
后,npm 依据 dependencies 和 devDependencies 属性中指定的包来确定第一层依赖,npm 2 会依据第一层依赖的子依赖,递归装置各个包到子依赖的 node_modules 中,直到子依赖不再依赖其余模块。执行结束后,咱们会看到 ./node_modules
这层目录中蕴含有咱们 package.json 文件中所有的依赖包,而这些依赖包的子依赖包都装置在了本人的 node_modules 中,造成相似于上面的依赖树:
这样的目录有较为显著的益处:
1)层级构造非常明显,能够分明的在第一层的 node_modules 中看到咱们装置的所有包的子目录;
2)在已知本人所需包的名字以及版本号时,能够复制粘贴相应的文件到 node_modules 中,而后手动更改 package.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 官网文档,次要的构造如下:
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 中的配置如下:
"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 官网文档
package-lock-json 官网文档
npm 文档总结
npm-pack
招贤纳士
政采云前端团队(ZooTeam),一个年老富裕激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 50 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员形成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端利用、数据分析及可视化等方向进行技术摸索和实战,推动并落地了一系列的外部技术产品,继续摸索前端技术体系的新边界。
如果你想扭转始终被事折腾,心愿开始能折腾事;如果你想扭转始终被告诫须要多些想法,却无从破局;如果你想扭转你有能力去做成那个后果,却不须要你;如果你想扭转你想做成的事须要一个团队去撑持,但没你带人的地位;如果你想扭转既定的节奏,将会是“5 年工作工夫 3 年工作教训”;如果你想扭转原本悟性不错,但总是有那一层窗户纸的含糊… 如果你置信置信的力量,置信平凡人能成就不凡事,置信能遇到更好的本人。如果你心愿参加到随着业务腾飞的过程,亲手推动一个有着深刻的业务了解、欠缺的技术体系、技术发明价值、影响力外溢的前端团队的成长历程,我感觉咱们该聊聊。任何工夫,等着你写点什么,发给 ZooTeam@cai-inc.com