踩坑记:同样的package.json,为什么换个目录npm install就报错?

1 阅读2分钟

最近遇到一个迷惑的场景,同样一个项目在旧目录下删除 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自动安装超出预期的版本,而这种不兼容,往往在编译阶段才会暴露。

避坑原则总结如下:

  1. 版本范围符号(^、~)决定npm实际安装的版本,不是package.json写的版本就是最终版本;
  2. ^2.5.4 会自动安装2.x最新版,可能导致与其他依赖不兼容;
  3. 精确版本声明(只写版本号,不加符号)是最稳妥的方式,可避免自动升级带来的兼容问题;