最近遇到一个迷惑的场景,同样一个项目在旧目录下删除 node_modules目录后重新执行 npm install 可以继续正常运行。但是,将项目源码重新拷贝到新目录后,执行 npm install,却跑不起来了,直接报依赖库不兼容。
不知道各位前端同学是否遇到过。
背景
笔者最近就踩了这么一个坑,最开始发现是通过Jenkins发版时报错,但是本地一切正常,可以正常编译打包。经过反复测试,终于通过开头的方法重现了。
实际报错内容如下:
RollupError: "toValue" is not exported by "node_modules/vue/dist/vue.runtime.esm-bundler.js", imported by "node_modules/element-plus/es/components/time-picker/src/composables/use-time-picker.mjs"
解决
折腾半天,终于搞清楚了。问题不在于 npm缓存,而是package.json 依赖版本升级逻辑和package-lock.json的版本管理逻辑。
lock文件类似快照,进行了版本锁定。我本地有此文件,所以即使删除 node_modules目录后重新安装依赖,仍然按照相同的版本,就不会有问题。
为什么换了目录就不行了呢?
因为,没有将lock文件纳入git版本控制,也就是说云端代码库中没有lock文件,无法进行版本锁定。
为什么以前可以,而现在却不可以了呢?
因为还有一个前提条件,就是依赖的版本声明中允许升级小版本号(版本号前面加了^符号),而依赖库又升级了小版本,并引起了兼容性问题。
比如下面这样:
{
"dependencies": {
"element-plus": "^2.5.4",
"vue": "^3.2.45"
}
}
^ 和 ~ 的区别
^:允许升级小版本和补丁版本,不允许升级大版本~:允许升级补丁版本,不允许升级次版本和主版本
关于这两个符号的实际效果,大家可以到 node_modules 目录中查看安装后的实际的版本号。
总结
本次踩坑,本质是对npm版本范围声明逻辑的不了解——以为“安装正常”就代表“版本兼容”,却忽略了版本范围符号会让npm自动安装超出预期的版本,而这种不兼容,往往在编译阶段才会暴露。
避坑原则总结如下:
- 版本范围符号(^、~)决定npm实际安装的版本,不是package.json写的版本就是最终版本;
- ^2.5.4 会自动安装2.x最新版,可能导致与其他依赖不兼容;
- 精确版本声明(只写版本号,不加符号)是最稳妥的方式,可避免自动升级带来的兼容问题;