在前端项目中为什么“写死版本号”不能完全替代 lock 文件

2 阅读2分钟

案例:

假设在开发一个项目,package.json 中非常严谨地“写死”了直接依赖的版本:

"dependencies": {
  "awesome-ui": "1.0.0" 
}

第一阶段:开发环境(正常)

当第一次安装时,awesome-ui@1.0.0 内部依赖了一个处理颜色的库 tiny-color,它在 awesome-ui 的配置里是这样写的:"tiny-color": "^2.1.0"。

此时,包管理器下载了 tiny-color@2.1.0。项目运行完美,颜色显示正常。

第二阶段:线上部署(有问题)

两周后,要进行线上发布。由于没有提交 Lock 文件,服务器执行 npm install 时会重新解析依赖树:

  1. 识别到 awesome-ui 依然是 1.0.0(因为写死了)。
  2. 扫描 awesome-ui 的依赖,发现它需要 tiny-color@^2.1.0。
  3. 关键点: 此时 tiny-color 刚刚发布了 2.2.0 版本,这个版本虽然号称是兼容更新,但由于作者疏忽,引入了一个针对旧版浏览器的 Bug,或者更改了某个 API 的返回格式。
  4. 包管理器根据 ^ 规则,自动安装了最新的 tiny-color@2.2.0。

结果: 代码一行没改,直接依赖的版本号也没变,但线上版本因为间接依赖的升级直接黑屏或样式错乱。

对比:Lock 文件的“救命”作用

如果当时提交了 Lock 文件(如 package-lock.json),情况会完全不同。

1. 结构化快照

Lock 文件会像下面这样记录:

"awesome-ui": {
  "version": "1.0.0",
  "dependencies": {
    "tiny-color": "2.1.0" // 注意:这里被强制固定在了 2.1.0
  }
}

即便 tiny-color 发布了 10.0.0,服务器在安装时也会无视最新版,严格按照 Lock 文件里的记录下载 2.1.0。

2. 内容指纹(Integrity)

假设 tiny-color 的作者不是发布了新版,而是偷偷在原有的 2.1.0 版本里注入了恶意脚本并重新上传(虽然极罕见,但理论存在)。

  • 只写死版本: 包管理器检测到版本号匹配,直接下载恶意代码。
  • 有 Lock 文件: Lock 文件记录了原始代码的 Sha512 哈希值。安装时包管理器会发现下载的文件哈希对不上,立即报错并停止安装,保护了系统安全。

总结

变更点只写死版本号有 Lock 文件
项目依赖 A保持 1.0.0保持 1.0.0
A 的依赖 B被自动升级到 2.2.0 (可能不兼容)被锁定在 2.1.0 (安全)
依赖安全性无法验证文件内容是否被篡改通过 Hash 校验确保文件未变

“写死版本号”只能保证package.json的直接依赖没变,但不能保证其间接依赖不变。而 Lock 文件会把所有依赖的版本号都确定下来,保证所有的包的版本都是不变的。