首先这是一个问题,但通过标题没法很好的表述出来,而大多数童鞋应该不会遇到这样的问题,然而我还是决定记录下来。
我们知道,npm3把所有依赖模块路径拍扁了(工程目录下的node_modules
出现了很多package.json
中没有声明的模块),解决了windows下路径名过长的问题,更使得公共依赖被充分共享。但如果多个模块依赖了同一个模块的不同版本,后安装的模块,为了不和别人冲突,就只能将这个依赖安装在自己的node_modules
下。
这个问题来源于我们实际某个基于VueJS的复杂项目的某次意外,为了使广大前端更容易理解,我们使用jquery作为栗子。
假设,我们用webpack开发一个小页面,需要用到jquery和一个jquery插件(假设叫jplugin)。假设这个jquery插件,遵循了ES6规范,它的package.json
声明依赖的jquery版本是^2.0.0(即>=2.0.0, <3.0.0):
|
|
它的主模块,应该会有类似这么几句:
而我们的项目直接依赖的jquery版本是^1.11.0(即>=1.11.0, <2.0.0):
|
|
很明显,^2.0.0
和^1.11.0
并不能兼容。那么,在npm i
之后,依赖目录大致是这样的(无关文件已省略):
|
|
我们的主模块代码应该会有类似这么几句:
最终运行代码,浏览器会报错,提示jplugin
不是一个function,也就是$('#someId').jplugin()
这句出错。
为什么呢?
webpack打包依赖时,与node查找依赖的方式一致,顶级依赖写法(比如这里的jquery
)首先从当前目录的node_modules
下查找,如果不存在,则向上级目录的node_modules
下查找,如果不存在则继续向上,以此类推。
在我们这个例子里,jplugin
模块本身查找到的jquery
版本是2.0.0
,在这个2.0.0
的jquery
上才会存在jplugin
这个扩展。而我们的主模块查找到的jquery
版本是1.11.0
版本,在这个1.11.0
版本的jquery
上调用jplugin
就出错了。
说到这里,还并没有引出本文的主题,不过也能够解释依赖版本冲突导致发生奇怪问题的现象了。
继续这个例子,当我们发现版本依赖有问题时,我们要处理它。假设我们把项目直接依赖的jquery升级到2.0.0,并不会导致其他兼容问题,我们执行npm i jquery@2.0.0 --save
. 我们看一下现在的目录结构:
|
|
上层的jquery已经升级到2.0.0了。我们很兴奋的再次运行页面,发现仍然还是报同样的错。
其实问题的原因是一样的,虽然大家都是2.0.0版本的jquery,但 此jquery非彼jquery,大家拿到的并不是同一个jquery对象。
其实,此时只要把根目录的node_modules
删除,再次执行npm i
,看看最终的目录结构:
如此一来,大家拿到的就是同一个jquery对象啦,最后页面运行正常了~
这个问题,才是本文要说的 依赖冗余 的问题,即,曾经由于依赖冲突导致安装了同个模块的多个版本,修正依赖版本后,再次npm i
,仍然解决不了问题,删除全部node_modules
重新安装依赖才正常。遗憾的是,npm并没有这么聪明,或者有其他原因,并没有在安装新模块后,自动帮我们梳理依赖关系。
那么,遇上这样的问题,就只能删除全部依赖彻底重新安装一次依赖吗?有没有更优雅的方式?
答案是npm dedupe
(或简写npm ddp
).
这个命令会重新计算所有依赖关系,把可以公用的依赖上升到顶级node_modlues
,并删除底层不必要的 副本。注意,需要整理的模块越多,所花的时间也越长。
因此,建议在你的构建过程中添加此命令,从而避免这种问题的发生。
ps: npm update
命令也同样存在此问题。
ps: 在写这篇文章之前,google到 玩转npm 这篇文章, 蛮有参考价值,建议阅读。