前端工程 | 无 lock 的灾难

1,067 阅读2分钟

前言

在最近的 React Native 项目中,由于项目中 git 提交时忽视了 yarn.lock 文件,引发了项目启动灾难。本文使用 verdaccio 搭建私有库复现灾难现场,强调 lock 文件的重要性,同时补充对版本号简单介绍。

注意

文章中涉及的实验项目已经存放到 github,操作步骤请看项目 readme。贪婪的骗赞又骗 star

场景描述

  1. 项目引入了第三个库 @localhost/a
  2. 第三方库 @localhost/a 引入了另一个库 @localhost/b,版本为 ^1.0.0
  3. @localhost/b 库的作者发布了 1.1.0 版本却没有向下兼容
  4. 此时如果我们的项目中没有 lock 文件的支持,那么就可能出现项目奔溃

场景模拟

实验中使用 yarn 引入库

搭建

  1. 安装 npm 私服
# 全局安装 verdaccio
yarn add -g verdaccio
# 启动 verdaccio
verdaccio
  1. 创建 @localhost/b 库,并发布 @localhost/b 库的 1.0.0 到本地私服
/**
 * @localhost/b 库中的 index.js
 */

function main() {
    console.log('你好世界')
}

module.exports = {
    main
}
  1. 创建 @localhost/a 库,从本地私服引入 @localhost/b 库,然后发布 @localhost/a 库的 1.0.0 版本到本地私服
/**
 * @localhost/b 库中的 index.js
 */

const bLib = require('@localhost/b')

function main() {
    bLib.main()
}

module.exports = {
    main
}
  1. 创建项目 project,从本地私服引入 @localhost/a 库
/**
 * 项目中的 index.js
 */

const aLib = require('@localhost/a')

function main() {
    aLib.main()
}

main()
  1. 运行项目的 index.js 文件,控制台打印 “你好世界”

复现

  1. 修改 @localhost/b 库,并发布 @localhost/b 库的 1.1.0 版本到本地私服
/**
 * @localhost/b 库中的 index.js
 */

function main() {
    console.log('你好世界')
}

- module.exports = {
-    main
- }
+ module.exports = main
  1. 回到项目,将 yarn.lock 文件重名为 yarn.lock.txt
  2. 删除 node_modules 然后执行 yarn 重新安装依赖库
  3. 运行项目的 index.js 文件
  4. 棒棒的,项目运行报错
  5. 重新删除 node_modules 然后将 yarn.lock.txt 中的内容复制到 yarn.lock 然后执行 yarn
  6. 运行项目的 index.js 文件
  7. 项目执行正常,控制台输出“你好世界”

版本

版本由“主版本号”、“次版本号”、“修订号”、“先行版本号”共同组成,即 <主版本号>[.<次版本号>][.<修订号>][-<先行版本号>]

版本大小

  • 主版本号、次版本号、修订号以数值比较。例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1
  • 主版本号、次版本号、修订号都相同时,有先行版本号的版本比较小。例如:1.0.0-alpha < 1.0.0
  • 主版本号、次版本号、修订号都相同时,并且两个版本都有先行版本号时,根据 ASCII 码的先后顺序比较先行版本号。例如:1.0.0-alpha1、1.0.0-alphaa,由于"alpha1" < "alphaa" 所以 1.0.0-alpha1 < 1.0.0-alphaa

安装规则

package.json 中的第三方库参照 SemVer 规则进行版本控制,当引入库的时候使用的高级范围语法控制库的版本时,当没有 lock 文件的情况下,每次根据 package.json 安装库时,都会在语法规则内安装最新的库

版本范围语法

连字符范围

语法
  • X.Y.Z[-先行版本号] - A.B.C[-先行版本号]
  • 如果范围中的某个版本号携带先行版本号,则此版本的先行版本参与比较,其他版本的先行版本不参与比较。否则所有的先行版本都不参与比较
例子
  • 1.2.3 - 2.3.4 :匹配>= 1.2.3并且 <= 2.3.4 的任意非先行版本

  • 1.2 - 2.3.4 : 匹配>= 1.2.0并且 <= 2.3.4 的任意非先行版本

  • 1.2.3 - 2.3 : 匹配>= 1.2.3 并且 < 2.4.0 的任意非先行版本

  • 1.2.3 - 2 : 匹配 >= 1.2.3 并且 < 3.0.0 的任意非先行版本

  • 1.2.3-beta - 2.0.0

    • 1.2.3-beta1 版本号相同,并且先行版本号 beta1 > beta ,符合范围
    • 1.2.4-beta 版本号不相同,不符合范围
    • 1.2.3-beaa 版本号相同,但是先行版本号 beaa < beta ,不符合范围

X范围

语法
  • X.Y.*X.**
  • xX* 含义相同
  • 范围中不能携带先行版本
例子
  • ""(空字符串) 等同于 *:匹配任意非先行版本
  • 1 等同于 1.*.*:匹配 >= 1.0.0 并且 < 2.0.0 的任意非先行版本
  • 1.2 等同于 1.2.*:匹配 >= 1.2.0 并且 < 1.3.0 的任意非先行版本

~范围

语法
  • ~X.Y.Z[-先行版本号]~X.Y~X
  • 只有当三个版本号都有值时才可以携带先行版本号
  • 如果携带先行版本号,则此版本的先行版本参与比较,其他版本的先行版本不参与比较。否则所有的先行版本都不参与比较
例子
  • ~1.2.3 :匹配 >= 1.2.3 并且 < 1.3.0 的任意非先行版本

  • ~1.2 :匹配 >= 1.2.0 并且 < 1.3.0 的任意非先行版本

  • ~1 :匹配 >= 1.0.0 并且 < 2.0.0 的任意非先行版本

  • ~0.0.1 :匹配 >= 0.0.1 并且 < 0.1.0 的任意非先行版本,即匹配 0.0.1

  • ~0.2.3 :匹配 >= 0.2.3 并且 < 0.3.0 的任意非先行版本

  • ~0.2 :匹配 >= 0.2.0 并且 < 0.3.0 的任意非先行版本

  • ~0 :匹配 >= 0.0.0 并且 < 1.0.0 的任意非先行版本

  • ~1.2.3-beta

    • 匹配版本号为 1.2.3 且先行版本号大于 beta的先行版本
    • 匹配 >= 1.2.3 并且 < 1.3.0 的任意非先行版本
    • 1.2.3-beta1 版本号为 1.2.3,并且先行版本号 beta1 > beta ,符合范围
    • 1.2.4-beta 版本号不为 1.2.3 ,不符合范围
    • 1.2.3-beaa 版本号为 1.2.3 ,但是先行版本号 beaa < beta ,不符合范围

^范围

语法
  • ^X.Y.Z[-先行版本号]^X.Y^X
  • 只有当三个版本号都有值时才可以携带先行版本号
  • 如果携带先行版本号,则此版本的先行版本参与比较,其他版本的先行版本不参与比较。否则所有的先行版本都不参与比较
例子
  • ^1.2.3 :匹配 >= 1.2.3 并且 < 2.0.0 的任意非先行版本

  • ^0.2.3 :匹配 >= 0.2.3 并且 < 0.3.0 的任意非先行版本

  • ^0.0.3 :匹配 >= 0.0.3 并且 < 0.0.4 的任意非先行版本

  • ^1.3 :匹配 >= 1.3.0 并且 < 2.0.0 的任意非先行版本

  • ^1 :匹配 >= 1.0.0 并且 < 2.0.0 的任意非先行版本

  • ^1.2.3-beta

    • 匹配版本号为 1.2.3 且先行版本号大于 beta的先行版本
    • 匹配 >= 1.2.3 并且 < 2.0.0 的任意非先行版本
    • 1.2.3-beta1 版本号为 1.2.3,并且先行版本号 beta1 > beta ,符合范围
    • 1.2.4-beta 版本号不为 1.2.3 ,不符合范围
    • 1.2.3-beaa 版本号为 1.2.3 ,但是先行版本号 beaa < beta ,不符合范围