npm采用语义化的版本号来描述依赖项的版本,其目的是让开发者尽可能地使用版本较新的模块,因为新版本往往会包含bug修复或者性能优化等。
然而实际的开发工作往往是:第一次安装好依赖之后就不再管它,此后的很长一段时间都使用第一次下载下来的版本。即使后来团队里一直有新人加入,还是一直沿用两三年前的一套模块。后续的项目开发除非明确发现了依赖模块中的bug,否则开发者一般不会有计划地升级依赖版本,就像很多用户关闭了Windows自动更新一样。
这听起来很正常,既然现有的依赖都能正常工作,为什么要花时间去升级它呢?但日积月累,版本之间的差异可能会给后续升级造成麻烦。
还是以一段实际经历为例,当笔者参加某个Web项目的开发时,计算机上已经安装了最新的Node环境(当时是Node v7.5)。将项目下载到本地,安装完成后运行npm start时,出现了类似下面的错误。
eventEmitter.emit("begin") ^ TypeError: Cannot read property 'emit' of undefined
项目组里的其他同事没有出现这个问题,经过排查,原因是node_modules中的一个模块socket.io(一个著名的WebSocket模块)中有如下的代码。
var eventEmitter = process.eventEmitter;
Node在6.x及之前的版本可以使用process.eventEmitter来访问Events模块,但这种写法已经在7.0及之后的版本废弃,而项目中使用的socket.io模块版本过低,使用的还是旧的写法,因此在运行时出现了错误。而其他同事计算机上的Node版本还停留在6.X,所以没有出现这个问题。
首先想到的解决方案自然是升级socket.io的版本,但它并不直接被项目代码依赖,而是作为karma(一个测试脚手架)的依赖项存在的。因此就变成了需要升级karma,但karma的版本又跟很多其他的模块绑定在一起,甚至还要升级webpack的配置文件,最终笔者暂时放弃了升级,改成手动修改源代码。
// 将第三方模块中所有的 var eventEmitter = process.eventEmitter; // 替换成 var eventEmitter = require("event")