前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >npm 依赖管理中被忽略的那些细节

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

作者头像
政采云前端团队
发布于 2020-08-26 09:43:05
发布于 2020-08-26 09:43:05
2.6K00
代码可运行
举报
文章被收录于专栏:采云轩采云轩
运行总次数:0
代码可运行

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

本文首发于政采云前端团队博客:npm 依赖管理中被忽略的那些细节 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 中包的前后顺序对于安装时有什么影响吗?这些问题平时大家可能没有注意过,今天我们就来一起研究一下吧。

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
node_modules/
├─┬ A
│ ├── C
├─┬ B
│ └── C

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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官方文档 (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 就像是我们的代码的一种保护机制一样,如果包存在的话就走存在的逻辑,不存在的就走不存在的逻辑。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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.xpackage.json (https://github.com/ant-design/ant-design/blob/master/package.json#L37) 中的配置如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"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,与其他几种依赖项不同,他不是一个键值对的对象,而是一个数组,数组里是包名的字符串,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{ 
  "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 中,并且不包括版本信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"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/)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 政采云技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
从 UNMET PEER DEPENDENCY 中理解依赖版本管理
笔者之前在开发模块分析工具,使用npm list命令时遇到 UNMET PEER DEPENDENCY 这个问题,在探究解决方法的时候对npm的包管理机制有了很多新的认识,分享一下过程中的思考。 UNMET PEER DEPENDENCY 是什么 ? 你在使用npm list命令的时候,可能遇到过下面这种npm ERR: UNMET PEER DEPENDENCY ERR 当你去检查依赖的树状结果,你会发现每一行npm ERR都有对应一行这样的结果: UNMET PEER DEPENDENCY,翻译过来
用户1097444
2022/06/29
5.4K0
从 UNMET PEER DEPENDENCY 中理解依赖版本管理
开发中遇到过的 NPM 疑惑解答
https://segmentfault.com/a/1190000039289332
@超人
2021/05/24
1.5K0
开发中遇到过的 NPM 疑惑解答
你不知道的npm
作为 node 自带的包管理器工具,在 nodejs 社区和 web 前端工程化领域发展日益庞大的背景下,npm已经成为每位前端开发同学必备的工具。
前端森林
2020/04/23
1.4K0
你不知道的npm
前端工程化 - 剖析npm的包管理机制(完整版)
现如今,前端开发的同学已经离不开 npm 这个包管理工具,其优秀的包版本管理机制承载了整个繁荣发展的NodeJS社区,理解其内部机制非常有利于加深我们对模块开发的理解、各项前端工程化的配置以加快我们排查问题(相信不少同学收到过各种依赖问题的困扰)的速度。
ConardLi
2019/12/19
3.1K0
开发者必看:揭开 NPM 依赖管理的复杂面纱
今天聊一个很少被提及的话题 —— 「依赖管理」(Dependencies Management) 。
童欧巴
2024/04/17
1K0
开发者必看:揭开 NPM 依赖管理的复杂面纱
你需要知道的几类npm依赖包管理
在我们日常的node开发中,我经常会依赖于这样或那样的第三方包,而本章就来讨论一下如何管理node项目中的依赖。 什么是NPM npm(node package manager)是nodejs的包管理
用户1687375
2018/06/08
2.8K0
npm install 原理分析
开门见山,npm install 大概会经过上面的几个流程,本篇文章来讲一讲各个流程的实现细节、发展以及为何要这样实现。
ConardLi
2019/12/18
9.7K1
关于 npm 和 yarn 总结一些细节
Searches the local package tree and attempts to simplify the overall structure by moving dependencies further up the tree, where they can be more effectively shared by multiple dependent packages. For example, consider this dependency graph: a +-- b <-- depends on c@1.0.x | `-- c@1.0.3 `-- d <-- depends on c@~1.0.9 `-- c@1.0.10 In this case, npm dedupe will transform the tree to: a +-- b +-- d `-- c@1.0.10 Because of the hierarchical nature of node's module lookup, b and d will both get their dependency met by the single c package at the root level of the tree. 复制代码 // npm7 以后微调 // 在保持上述原则的基础上,升级了如下细微的规则: In some cases, you may have a dependency graph like this: a +-- b <-- depends on c@1.0.x +-- c@1.0.3 `-- d <-- depends on c@1.x `-- c@1.9.9 During the installation process, the c@1.0.3 dependency for b was placed in the root of the tree. Though d's dependency on c@1.x could have been satisfied by c@1.0.3, the newer c@1.9.0 dependency was used, because npm favors updates by default, even when doing so causes duplication. Running npm dedupe will cause npm to note the duplication and re-evaluate, deleting the nested c module, because the one in the root is sufficient. To prefer deduplication over novelty during the installation process, run npm install --prefer-dedupe or npm config set prefer-dedupe true. Arguments are ignored. Dedupe always acts on the entire tree. Note that this operation transforms the dependency tree, but will never result in new modules being installed. Using npm find-dupes will run the command in --dry-run mode. Note: npm dedupe will never update the semver values of direct dependencies in your project package.json, if you want to update values in package.json you can run: npm update --save instead.During the installation process, the c@1.0.3 dependency for b was placed in the root of the tree. Though d's dependency on c@1.x could have been satisfied by c@1.0.3
PHP开发工程师
2022/06/01
6940
在nodejs中使用npm包管理器
nodejs的强大一方面在于语言特性和V8引擎结合焕发的生命活力,另一方面就是强大的第三方包。除了nodejs服务端应用之外,前端的许许多多lib都加入了第三方包的阵营。
用户2323866
2021/06/22
1K0
npm5 新版功能特性解析及与 yarn 评测对比
前言 前段时间 npm 发布了 5.0 版本,提供了自动记录依赖树,下载使用强校验,重写缓存系统等功能升级和改造,吸引了不少关注。本文将对 npm5 的新功能和变化点在进行实践使用后进行介绍和总结,并和 yarn 进行简单对比。 更新一览 通过官方的 Release note 我们可以看到 npm5 的主要新功能和大改动主要有下面几点(后面将会详细介绍): 默认新增 package-lock.json 来记录依赖树信息,进行依赖锁定,并使用新的 shrinkwrap 格式。 --save 变成了默认参数,执
马铖
2018/01/15
5.7K0
npm5 新版功能特性解析及与 yarn 评测对比
package.json 配置完全解读
package.json 是前端每个项目都有的 json 文件,位于项目的根目录。许多脚手架在搭建项目时也会自动帮我们自动初始化好 package.json。
Leecason
2022/12/16
3K0
package.json 配置完全解读
package.json 知多少?
在 Node.js 中,模块是一个库或框架,也是一个 Node.js 项目。Node.js 项目遵循模块化的架构,当我们创建了一个 Node.js 项目,意味着创建了一个模块,这个模块必须有一个描述文件,即 package.json。它是我们最常见的配置文件,但是它里面的配置你真的有详细了解过吗?配置一个合理的 package.json 文件直接决定着我们项目的质量,本章就带大家了解下 package.json 的各项详细配置。
ConardLi
2019/12/02
1.9K0
关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?
很长时间没有更新原创文章了,但是还一直在思考和沉淀当中,后面公众号会更频繁地输出一些前端工程相关的干货,希望对大家有一些启发,也希望在实际的工作当中帮助大家提升效率。
用户3806669
2021/03/10
3.2K0
前端包管理工具 npm yarn cnpm npx
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
虎妞先生
2022/10/27
9150
前端包管理工具 npm yarn cnpm npx
从npm发展历程看pnpm的高效
执行npm install 之后。npm 帮我们下载对应的依赖包并解压到本地缓存,然后构造node_modules目录结构,写入依赖文件,对应的node_modules内部结构也经历了几个版本的变化。
虎妞先生
2022/09/28
2.1K0
从npm发展历程看pnpm的高效
npm-shrinkwrap锁定依赖
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
奋飛
2019/08/15
1.1K0
2018 年了,你还是只会 npm install 吗?
作者:rianma | 腾讯web前端开发工程师 nodejs 社区乃至 Web 前端工程化领域发展到今天,作为 node 自带的包管理工具的 npm 已经成为每个前端开发者必备的工具。但是现实状况是,我们很多人对这个nodejs基础设施的使用和了解还停留在: 会用 npm install 这里(一言不合就删除整个 node_modules 目录然后重新 install 这种事你没做过吗?) 当然 npm 能成为现在世界上最大规模的包管理系统,很大程度上确实归功于它足够用户友好,你看即使我只会执行 inst
Techeek
2018/03/26
6.7K7
NPM项目管理
NPM全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于Maven 。
乐心湖
2020/07/31
1.1K0
很多人上来就删除的package-lock.json,还有这么多你不知道的!
看完本文,你将从整体了解依赖版本锁定原理,package-lock.json 或 yarn.lock 的重要性。首先要从最近接连出现两起有关 npm 安装 package.json 中依赖包,由于依赖包版本更新 bug 造成项目出错问题说起。
coder_koala
2021/04/21
4K0
你真的了解package.json吗?
然后,在写这系列文章时,发现有些操作需要用到package.json中的属性。然后,有些属性看起来人畜无害,但是用起来却需要查很多的资料。所以,就想着。写一篇或者两篇关于package.json的文章。
前端柒八九
2024/02/17
3020
你真的了解package.json吗?
相关推荐
从 UNMET PEER DEPENDENCY 中理解依赖版本管理
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档