谈谈npm依赖管理
引言
现在的前端开发几乎都离不开nodejs的包管理器npm,比如前端在搭建本地开发服务以及打包编译前端代码等都会用到。在前端开发过程中,经常用到npm install来安装所需的依赖,至于其中的技术细节未做过多的理解,下面就来说说node包管理器npm。
依赖安装npm install
使用npm来管理nodejs的包依赖,需要在项目根目录下提供一个package.json文件,其中与包依赖相关的字段有:
- dependencies: 指定项目运行时所依赖的模块
- devDependencies: 指定项目开发时所需要的模块
- peerDependencies:指定当前模块所在的宿主环境所需要的模块及其版本
其中,大家应该都知道:通过命令npm install --save $package来安装运行时依赖的模块,npm install --save-dev $package来安装本地开发时所依赖的模块。
需要指出的是,通过npm install来安装包依赖时,dependencies和devDependencies是有区别的,具体如下:
dependencies指定的包只在以下两种情况下被安装:
- 在一个含有
package.json的目录下执行npm install - 在任意一个目录中执行
npm install $package
- 在一个含有
devDependencies指定的包被安装的情况如下:
- 在一个含有
package.json的目录下执行npm install;但是,执行npm install --production该命令是不会安装devDependencies指定的包的 - 执行
npm install $package --dev时会安装对应的依赖包; 但是, 执行npm install $package时是不会安装devDependencies指定的包的
- 在一个含有
所以:
我们经常在通过
npm install $package来安装一个依赖包时,npm只会安装该依赖包的package.json文件中的dependencies所指定的依赖包,devDependencies是不会被安装的。
另外,通过npm install $package在安装指定依赖包时,该包package.json中的peerDependencies所指定的依赖包及其版本,则是对依赖该$package包的宿主环境的要求。
若宿主环境没有安装对应的包或者安装的版本不满足要求,npm在安装过程中给出错误警告。
npm2与npm3+ 安装依赖的区别
npm在安装依赖包时,会将依赖包下载到当前的node_modules目录中。每个包安装过后都会有自己的node_modules吗?这又涉及到不同版本的npm其对包依赖的目录组织结构有所不同。
npm2 依赖安装
npm2依赖安装的时候比较简单直接,直接按照包依赖的树形结构下载填充本地目录结构,也就是说每个包都会将该包的依赖组织到当前包所在的node_modules目录中。
npm2这样设计的原因可能是引用文章[2]的一句话:
因为 npm 设计的初衷就是考虑到了包依赖的版本错综复杂的关系,同一个包因为被依赖的关系原因会出现多个版本,简单地填充结构保证了无论是安装还是删除都会有统一的行为和结构。
这样,一个项目App 里模块 A 和 C 都依赖 B,无论被依赖的 B 是否是同一个版本,都会生成下面的目录结构:

很明显:
这种依赖的组织结构,虽然简单的实现多版本兼容,但是可能造成目录结构嵌套比较深,并且很可能造成相同模块的大量冗余问题。
npm3+依赖安装
npm3则对依赖安装进行了改造,采用”扁平结构“的思路来组织依赖包的目录结构。具体的就是npm install时:
按照 package.json 里依赖的顺序依次解析,遇到新的包就把它放在第一级目录,后面如果遇到一级目录已经存在的包,会先判断版本,如果版本一样则忽略,否则会按照 npm2 的方式依次挂在依赖包目录下。
以上面的例子看一下对比结果:

这样,npm3+采用这种扁平结构部分的解决了npm2的痛点。
为什么说是部分解决呢,npm3+并没有完美解决npm2中的问题,在某些情况下甚至会退化到npm2的行为。
例如项目App里依赖模块A、C、D、E, 其中A、C、D依赖模块B v2.0, E依赖模块B v1.0,生成的npm3结构如下

可以看到B、C、D模块下包含了各自依赖的B v2.0,存在代码冗余的情况。
那么是否可以解决这代码冗余问题呢,在E模块依赖的模块B升级到V2.0前提下,我们可以通过npm dedupe把所有二级的依赖模块B v2.0重定向到一级模块B下,如下图所示:

node_modules路径查找
上面说到了npm2与npm3依赖包组织结构的不同;那么如何找到对应的依赖包呢,例如项目访问webapck依赖包:
const webapck = require('webpack')
那么nodejs是怎么找到webpack模块的呢,这就是涉及到依赖包的路径查找问题。具体如下:
如果传递给
require()的参数不是nodejs的核心模块,也不是以/、../或./开头,
那么nodejs会尝试从当前模块所在目录开始,尝试在它的 node_modules 文件夹里加载相应模块,根据模块的package.json来加载对应的js文件;如果没有找到,那么就再向上一级目录移动,直到文件系统的根目录为止。
例如,假设在/home/wonyun/projects/foo.js 文件里调用了 require('bar.js') ,那么 nodejs 查找其位置的顺序依次为:
/home/wonyun/projects/node_modules/bar.js/home/wonyun/node_modules/bar.js/home/node_modules/bar.js/node_modules/bar.js
若果追踪到文件系统的根目录也没有找到对应的依赖,那么nodejs就会找不到对应模块的报错。
声明一下:以上图片使用文献[2]的图片加以说明。
参考文献
1、【npm】详解npm的模块安装机制
2、npm2 npm3 yarn 的故事
3、npm&&npmscript&&gulp&&webpack
4、What's the difference between dependencies, devDependencies and peerDependencies in npm package.json file?
谈谈npm依赖管理的更多相关文章
- 探讨npm依赖管理之peerDependencies
引言 想必前端同学对npm的devDependencies和dependencies都比较熟悉,但是对peerDependencies可能就有点陌生,尤其是没有写过npm包插件的同学,比如之前使用gr ...
- npm依赖管理:冗余,依赖树
npm的依赖树查询:原理都是查询文件夹node_modules的结构.比如mac的node_modules位置在/usr/local/lib下.具体项目的node_modules位置位于项目根目录下. ...
- nodejs Yarn替代npm的包管理——快速、安全、可靠性高的依赖管理
Yarn能帮你解决的五件事 转自: http://www.qingpingshan.com/jb/javascript/185590.html 长话短说(TL;DR):在 JavaScript 领域有 ...
- 用CocoaPods做iOS程序的依赖管理(转摘)
转摘自:http://blog.devtang.com/blog/2014/05/25/use-cocoapod-to-manage-ios-lib-dependency/ 文档更新说明 2012-1 ...
- Composer : php依赖管理工具
原始时代 我记得在当时用php的时候还没有composer,只有个pear,但是不好用呀,还不如直接在互联网上到处复制代码了,更快更不容易出错,当时也没有github这么好的社区工具了 总结如下 代码 ...
- crossplatform---bower解决js的依赖管理
从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发.Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎.chrome浏 ...
- 用CocoaPods做iOS程序的依赖管理
CocoaPods简介 每种语言发展到一个阶段,就会出现相应的依赖管理工具,例如Java语言的Maven,nodejs的npm.随着iOS开发者的增多,业界也出现了为iOS程序提供依赖管理的工具,它的 ...
- bower解决js的依赖管理
bower解决js的依赖管理 前言: 一个新的web项目开始,我们总是很自然地去下载需要用到的js类库文件,比如jQuery,去官网下载名为jquery-1.10.2.min.js文件,放到我们的项目 ...
- Composer PHP 依赖管理工具
composer 是 PHP 用来管理依赖(dependency)关系的工具.你可以在自己的项目中声明所依赖的外部工具库(libraries),Composer 会帮你安装这些依赖的库文件. 依赖管理 ...
随机推荐
- UML图之时序图
时序图(Sequence Diagram)是显示对象之间交互的图,这些对象是按时间顺序排列的.顺序图中显示的是参与交互的对象及其对象之间消息交互的顺序.时序图中包括的建模元素主要有:角色(Actor) ...
- bootstrapTable服务器端分页
bootstrap table加载:无论是服务器分页还是客户端分页,重新加载表格前请一定先销毁!销毁!销毁! !!销毁表格:: 客户端分页: 1. 表格销毁 $('#tableID').bootst ...
- django settings配置文件ALLOWED_HOSTS
ALLOWED_HOSTS列表为了防止黑客入侵,只允许列表中的ip地址访问 填写上“*”可以使所有的网址都能访问Django项目了,项目测试的时候,可以这么做.这样就失去了保护
- 7A - Max Sum
Given a sequence a[1],a[2],a[3]......a[n], your job is to calculate the max sum of a sub-sequence. F ...
- Ajax使用的五步法
Ajax使用的五步法 <script type="text/javascript"> //用于保存XMLHttpRequest对象的变量,由于整个过 ...
- 【Java】a++,++a 区分记忆
写了个例子测试: package com.xdsux.java.basetest; public class BaseTest1 { public static void main(String[] ...
- LOL新版符文 怎么查看队友的符文配置?
LOL怎么看其他玩家符文? 像我这种名字都懒的打的, 直接去WeGame复制昵称. 然后在LOL主界面点生涯, 之后搜索 光头强xian生
- MySQL远程连接失败(错误码:2003)
一 环境信息 服务器系统:Ubuntu 18.04 服务器MySQL版本:14.14 Distrib 5.7.25 本地系统:Kali Linux 本地客户端:python3交互模式 本地开发环境:p ...
- ntelliJ IDEA 仿照vs2017快捷键设置,以及字体颜色设置
因后期工作需要使用java技术栈,所以近期抽空下载了intelliJ IDEA工具,但是作为一个Net开发者,在使用了vs以后,感觉在使用别的开发工具感觉就是没法和vs相比,毕竟vs被称为宇宙最强id ...
- 在Java中使用Socket模拟客户端和服务端(多线程)
1:Socket与ServerSocket的交互 2.Socket和ServerSocket介绍 Socket 构造函数 Socket() Socket(InetAddress address, in ...