NPM 版本控制小坑

我们在项目中引入 NPM 模块时,需要对依赖进行有效的版本控制,否则当某个依赖做了不向下兼容的升级,就会造成我们项目出错。同样,我们在开发 NPM 模块时,也需要做好版本控制,以免给其他开发者造成困扰。

NPM 版本号规范

NPM 上所有模块都遵循 semver 2.0 规则。

简单规则如下:

版本格式:主版本号.次版本号.修订号,版本号递增规则如下:

  1. 主版本号:当你做了不兼容的 API 修改,
  2. 次版本号:当你做了向下兼容的功能性新增,
  3. 修订号:当你做了向下兼容的问题修正。

先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

对于预发版本,还可以使用连字符 - 加一连串以句点分隔的标识符来修饰。范例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。

基于这样的规则,我们在项目中选择 NPM 模块时候就可以对依赖进行有效的版本控制。

"dependencies": {

    "animationjs": "~0.1.5",

    "core-js": "^2.4.0",

    "httpurl": ">0.1.1",

    "kountdown": "<=0.1.2",

    "lazyimg": "0.1.2",

    "lie": "3.0.4-beta",

    "modals": "*"

}

除此之外,NPM 还拓展了 tags 功能来增强基于 semver 的版本控制。tags 允许开发者更有效地分发自己的模块。

npm install lodash@beta

选择合适的 NPM 模块版本

上面例子展示几种常用的版本范围选择风格,我们详细来了解一下:

  • ~x.y.z: 匹配大于 x.y.zz 的最新版
  • ^x.y.z: 匹配大于 x.y.zy.z 的最新版
    • x0 时,^x.y.z 等价于 ~x.y.z,即只会安装z 的最新版本;
    • xy0 时,^x.y.z 等价于 x.y.z,即只会安装x.y.z 版本;
  • >x.y.z<=x.y.z: 大于 x.y.z 的最大版本或与 x.y.z 最接近的版本
  • x.y.z: 选择 x.y.z
  • *: 任意版本,一般是最后一次正式发布版本(包括非 latest tag),不是最大版本号版本

这是最基本的模块选择,对于使用连字符 - 描述的预发版本,必须具体指定具体的先行版本号才安装。

由于所有 tags 公用一个命名空间,因此,我们在安装 NPM 模块时,很可能安装到其他 tags 下的模块。

我们以一个测试项目 tyd 为例:

通过 npm info <package_name> 可以查看某个 npm 模块的 tag 信息和版本信息。

$ npm info tyd

{ name: 'tyd',
  'dist-tags': { latest: '6.0.1-beta.4', beta: '5.0.7-beta', alpha: '5.1.1' },
  versions:
   [ '0.0.1-beta',
     '0.0.1',
     '0.0.2',
     '0.0.3',
     '0.0.4',
     '0.0.5',
     '0.1.0',
     '0.1.1',
     '0.2.1',
     '1.0.1',
     '1.1.1',
     '1.1.2',
     '1.1.3',
     '1.2.0',
     '1.2.2',
     '1.2.4',
     '1.3.0',
     '2.1.1',
     '3.1.0',
     '4.0.1',
     '4.0.2',
     '4.2.2',
     '4.3.1-beta'
     '5.0.1-beta.0.1',
     '5.0.1-beta.0.2',
     '5.0.1-beta.1.0',
     '5.0.1-beta.1.1',
     '5.0.1-beta.1.3',
     '5.0.1-beta.2.0',
     '5.0.3-beta',
     '5.0.3',
     '5.0.5-beta.1',
     '5.0.6',
     '5.0.7-beta',
     '5.0.8',
     '5.0.9-beta.0.1',
     '5.1.1',
     '5.1.2-beta.1',
     '6.0.1-beta.2',
     '6.0.1-beta.3',
     '6.0.1-beta.4',
     '6.1.2-beta.0.1' ],

     /* 其他信息省略 */
}

我们测试使用不同的版本范围选择风格(NPM 版本 3.9.3),实际安装的版本如下列表:

  1. *: 5.1.1 (该版本 tag 为 alpha)
  2. ~0.0.1: 0.0.5 (该版本 tag 为 beta)
  3. ^0.0.1: 0.0.1
  4. ~4.0.1: 4.0.2 (该版本 tag 为 beta)
  5. ^4.0.1: 4.2.2
  6. ^4.2.2: 4.2.2
  7. ~5.0.1-beta.0.1: 5.0.8
  8. ^5.0.1-beta.0.1: 5.1.1
  9. ~6.0.1-beta.2: 6.0.1-beta.4
  10. ^6.0.1-beta.2: 6.0.1-beta.4
  11. <=4.0.2: 4.0.2 (该版本 tag 为 beta)
  12. >6.0.1-beta.4: Error
  13. >6.0.1-beta.2: 6.0.1-beta.4

通过以上测试我们可以发现一些规律:

  1. NPM 默认不会匹配带有预发版本信息的版本号,例如上面例1、例5和例6;
  2. 不同的 tag 对于范围选择无影响,如例1、例4、例11;
  3. 当匹配范围的设置了一个预发版本号,正式版本仍然有最高优先级,只有当无法匹配到任何正式版本后才会匹配预发版本,如例7、例8、例9、例10;
  4. NPM 无法跨正式版本号匹配预发版本,如例12、例13。

发布 NPM 模块时的坑

很多时候我们会发布预发模块到 NPM。这时候,如果我们只为这个预发版本打了一个 beta tag,那么,当一个用户升级依赖时,很有可能升级到一个预发的版本而出错。如上例中1、2、4,用户设置了一个正式版本的范围,最终却安装到一个预发版本。

所以发布 NPM 模块时,最好严格遵循 semver 规范,如果是预发版本,请用连字符 - 加其他信息命名预发版本号,这样,NPM 会默认匹配正式版本,反正用户使用了不稳定的模块。

那么,NPM tags 有什么用?

额,我也没有想明白。。