blog icon indicating copy to clipboard operation
blog copied to clipboard

请锁定 package.json 里的模块版本号

Open lmk123 opened this issue 7 years ago • 9 comments

用过 NPM 的朋友可能都有过下面的经历:

你创建了一个项目,用 npm 安装了一些第三方模块。一个月过去了,另外一个人想跟你一起开发这个项目,但他却无法在他的电脑上正常运行你的项目,尽管在你自己的电脑上一切如常。

你可能已经猜到了,这是因为你自己电脑上的第三方模块和他电脑上的第三方模块的版本不一致导致的。

出现这个问题的过程是这样的:当你第一次安装一个模块的时候,假设它的版本号是 1.0.0,但在 package.json 里 npm 会自动帮你设置一个版本范围:^1.0.0。一个月过去了,这个模块已经更新到了 1.1.0,但是你从来没有更新过,可是当别人在安装你的项目的时候,npm 会在他的电脑上安装这个版本范围内的最新版本,也就是 1.1.0,这里面可能会包含一些 「Breaking Changes」,所以就导致了在你电脑上好好的项目,在别人的电脑上却跑不起来。

即使这个项目只有你一个人开发,你也不应该依赖 npm 自动帮你设置的“版本范围”——一旦你一不小心运行了 npm update 来升级你的第三方依赖,你仍然会遇到上面的问题,更糟糕的是,你根本不知道你升级前用的是哪个版本的第三方模块,你可能不得不去挨个翻看你依赖的模块们有哪些 「Breaking Changes」并逐个更改,等你差不多改完的时候也该下班了,然而跟别人说好今天要完成的功能你还一点都没有开始做。

所以,聪明的开发者都会在 package.json 里锁定模块的版本号,以保证在任何情况下大家安装的模块都是一致的。

即使你的项目是用 Yarn 安装模块的,我仍然建议你锁定你的版本号。

在你使用 Yarn 安装模块的时候,这个模块的版本号会被写在 yarn.lock 这个文件里,所以当其他人也使用 Yarn 安装项目里的模块时,Yarn 会优先读取 yarn.lock 里的模块版本号以保证大家安装的模块版本是一致的——

但如果其他人使用 npm 安装了你项目的模块呢?

跟前文说到的情况一样,用 npm 安装你项目模块的人仍然会安装到最新版本的模块—— npm 并不会从 yarn.lock 文件里读取你所安装的模块的版本,这就又会导致前文提到的问题。即使你在项目的 README.md 里提到了要用 Yarn 安装项目的模块,也无法保证会没人使用 npm 安装模块,因为大家都认为,Yarn 和 npm 是兼容的。

所以,无论项目使用的是 npm 还是 Yarn,我都建议锁定模块的版本号,这样一来就可以保证无论大家在何时使用哪种包管理器,所安装的模块都是一致的。

lmk123 avatar Mar 22 '17 11:03 lmk123

不建议锁定,理由见:https://zhuanlan.zhihu.com/p/22934066

afc163 avatar Mar 22 '17 11:03 afc163

@afc163 文章说的很有道理,但是一个项目里并不是所有模块都遵循 semver 语义化版本的,或者模块的作者在发布自己的模块的时候并不知道自己引入了一个 bug(即文中所说的“没有经过完善的测试”)。

我自己印象最深的经历就是在我使用 Babel 的时候。以前我在开发划词翻译的时候,几乎每次运行 npm update 升级了 Babel 的模块都会导致项目跑不起来;即使我本地不更新 Babel,但跑在 Travis CI 上的单元测试也会因为下载了新版本的 Babel 而导致测试不通过,我当时还发过一条 twitter 吐槽过哈哈:https://twitter.com/milklee5/status/689685684202766336

所以后来我就养成了锁定版本的习惯,但我会频繁使用 npm outdate 检查模块是否有新版本,并及时更新。虽然烦琐一些,但至少是可以回滚到上一版本的,因为我知道我上一次安装的版本号是多少。

lmk123 avatar Mar 22 '17 11:03 lmk123

我个人赞同锁定的方案,有些BUG Fix对你的项目来说,可能是breaking change;有些团队在对于较小的breaking change时,不一定愿意更新大版本号。

izee avatar Mar 27 '17 08:03 izee

yarn 的lock也是只锁定一层版本号,对依赖的子依赖无法控制,建议还是用npm官方的shrinkwrap

klouskingsley avatar Mar 28 '17 06:03 klouskingsley

@klouskingsley Yarn 并不是仅仅只包含第一层依赖的版本号吧?yarn.lock 文件里列出了所有依赖和依赖的子依赖的版本。是因为我们使用的 Yarn 的版本不同吗?我用的是 0.21.3

lmk123 avatar Mar 28 '17 06:03 lmk123

@lmk123 可以看yarn本身的lock,在第二层depedencies中还是使用的semver,没有严格的限定版本。还有假设有第三层依赖的话,yarn也没有列出(未验证)。 而npm-shrinkwrap.json中严格限定了所有包的版本,这点可以参考npm的npm-shrinkwrap.json文件。

klouskingsley avatar Mar 28 '17 13:03 klouskingsley

@klouskingsley yarn.lock 里的第二层的依赖虽然用的是 ^ 开头的版本号,但所有这些被依赖的模块也会列在第一层中,你可以在 Yarn 本身的 yarn.lock 文件里搜索一下。

yarn add webpack 生成的 yarn.lock 为例,我节选了一部分内容粘贴在下面,并且改了下顺序让依赖关系更明显:

wide-align@^1.1.0:
  version "1.1.0"
  resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.0.tgz#40edde802a71fea1f070da3e62dcda2e7add96ad"
  dependencies:
    string-width "^1.0.1"

string-width@^1.0.1, string-width@^1.0.2:
  version "1.0.2"
  resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
  dependencies:
    code-point-at "^1.0.0"
    is-fullwidth-code-point "^1.0.0"
    strip-ansi "^3.0.0"

code-point-at@^1.0.0:
  version "1.1.0"
  resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"

is-fullwidth-code-point@^1.0.0:
  version "1.0.0"
  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
  dependencies:
    number-is-nan "^1.0.0"

strip-ansi@^3.0.0, strip-ansi@^3.0.1:
  version "3.0.1"
  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
  dependencies:
    ansi-regex "^2.0.0"

lmk123 avatar Mar 29 '17 03:03 lmk123

@lmk123 是我看得不仔细了,多谢~

klouskingsley avatar Mar 29 '17 04:03 klouskingsley

但是yarn还是有点问题。 比如:这是我的package.json

{
  "name": "yarn0test",
  "version": "1.0.0",
  "description": "yarn test",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "uglify-js": "2.5.0",
    "webpack": "^1.14.0",
    "webpack-parallel-uglify-plugin": "^0.2.0"
  },
  "author": "h",
  "license": "MIT"
}

没有yarn.lock的时候,执行yarn生成的lock中有关uglify-js的版本是这样的

[email protected]:
  version "2.5.0"
  resolved "http://r.cnpmjs.org/uglify-js/download/uglify-js-2.5.0.tgz#4ab5d65a4730ecb7a4fb62d3f499e2054d98fba1"
  dependencies:
    async "~0.2.6"
    source-map "~0.5.1"
    uglify-to-browserify "~1.0.0"
    yargs "~3.5.4"

uglify-js@^2.6.2:
  version "2.8.18"
  resolved "http://r.cnpmjs.org/uglify-js/download/uglify-js-2.8.18.tgz#925d14bae48ab62d1883b41afe6e2261662adb8e"
  dependencies:
    source-map "~0.5.1"
    yargs "~3.10.0"
  optionalDependencies:
    uglify-to-browserify "~1.0.0"

uglify-js@~2.7.3:
  version "2.7.5"
  resolved "http://r.cnpmjs.org/uglify-js/download/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
  dependencies:
    async "~0.2.6"
    source-map "~0.5.1"
    uglify-to-browserify "~1.0.0"
    yargs "~3.10.0"

通过查看得知, /node_modules/uglify-js的版本是2.5.0 /node_modules/webpack/node_modules/uglify-js的版本是2.7.5 /node_modules/webpack-parallel-uglify-plugin/node_modules/uglify-js的版本是2.8.18

删除项目node_modules之后在执行yarn(此时有之前生成的yarn.lock)时 通过查看得知, /node_modules/uglify-js的版本是2.5.0 /node_modules/webpack/node_modules/uglify-js的版本是2.7.5 /node_modules/webpack-parallel-uglify-plugin/node_modules/uglify-js的版本是2.7.5

之后也删除node_modules之后试了几次/node_modules/webpack-parallel-uglify-plugin/node_modules/uglify-js的版本仍然是2.7.5。

这就造成了之后使用yarn add 添加新module的时候,对新生成的yarn.lock文件是不可靠的,并且有可能影响之前的module的锁定状态。

klouskingsley avatar Mar 29 '17 09:03 klouskingsley