这是最近才注意的一个问题。之前在上一家公司的时候,有个项目组的项目,经常发生有的人的电脑里安装项目依赖后无法启动项目的问题,然后每每都是让他从别的同事那里把node_modules文件夹打包拷贝过来,我刚去那个项目组的时候也碰到了这个问题,也是从别的同事那里拷贝了一份node_modules文件夹才能正常跑项目的,后来经过尝试发现好像如果我将npm命令的版本降低到2+再安装项目依赖的话就能正常跑了,但是这个显然是个很奇葩的现象,另外需要说的是他们那个项目也有package-lock.json这个文件的,这个项目因为不是一开始就在跟的项目,我也搞不拎清究竟是哪里出了问题。但是node包的版本管理依旧很重要,不管理的话更容易发生类似这样的问题。
一、不要直接修改package-lock.json/yarn.lock文件
如果要增删项目的依赖或者修改其版本号,不要手动直接去修改package-lock.json/yarn.lock文件,而要通过对应的npm命令或yarn命令进行操作:
1、通过npm增删包
# 添加项目依赖 npm i -S # 添加开发依赖 npm i -D # 删除项目依赖 npm uninstall -S # 删除开发依赖 npm uninstall -D
2、通过yarn增删包
# 通过yarn添加项目依赖 yarn add # 通过yarn添加开发依赖 yarn add --dev # 通过yarn删除依赖 yarn remove
通过这些命令去更新项目依赖信息的同事,不仅会自动更新package.json文件,还会同步更新package-lock.json文件(如果用的是npm)或yarn.lock文件(如果用的是yarn)。而且这里有个特点,通过这种方式更新的package.json文件里的项目依赖都是将依赖包按包名字母顺序排列的,这样比较好找包的版本信息。如果你打开一个项目的package.json文件发现里面的包名排列没有顺序乱七八糟的,那就说明这个人是直接自己手改。
二、指定node包到具体的小版本号
因为:
1、你不能信任第三方包都完全按照语义化版本号规则去更新版本号;
2、你不能信任按语义化版本号更新版本号的第三方包在修复bug或添加新功能时不会引入新的bug;
3、第三方包在更新版本号的时候可能会更新它对应的依赖包的版本号,第三方包依赖的第三方包的版本号变动又可能引发上面的问题。
三、不要在同一个项目里同时混用npm和yarn
假设你通过npm i -S a 命令添加并安装了node包a,并且实际安装的版本是1.0.1,这时候具体的版本信息是记录在package-lock.json文件里的,过了一个月另外一个同事通过yarn install 命令安装包a时,由于具体的版本信息在yarn.lock问及那种是不存在的,所以完全有可能这位同事实际安装的是1.1.2版本,这样就会造成包的版本管理混乱的问题,反过来也是一样,不再赘述。
四、团队开发时应当同统一node版本号
团队合作开发的项目中,大家各自电脑里的node版本号也需要进行统一,如果不统一,比如甲本地node –version 为9.2.1,乙本地使用node –version 为10.0.0,那么可能会有两种问题:
1、如果一个程序的版本遵循语义化版本的规定,那么a.b.c这样的版本号中,a的变动表示的是较大的版本升级(可能会有不向前兼容的新API等)b的变动表示的是较小的、向前兼容的功能更新,c的变动表示的是一些bug修复。但是我们不能保证第三方程序都严格按照这个语义化版本的规定进行版本号的命令,另外也无法确定只是更新了版本号的c部分的是否只是修复了已知bug而没有再次引入新的bug,所以我个人是建议直接把node版本号进行统一,而不是给出一个允许使用的node版本号区间。
2、这里还有一个问题,比如乙在本地添加了一个依赖包,该依赖包要求node版本号10.0.0及以上,由于乙本地node版本号为10.0.0所以可以正常安装该依赖了,但是甲本地的node版本号为9.2.1,就无法安装该依赖包了,团队合作开发的项目,我们当然是希望大家都可以在本地正常安装项目依赖并运行项目的。
如果你本地有多个项目分别要求不同的node版本的话,可以使用nvm这个命令行工具。nvm的安装及使用都是非常简单的。
# 安装nvm brew install nvm # 通过nvm安装指定版本的node nvm install v9.2.1 # 临时切换为指定版本的node环境 nvm use v9.2.1 # 设置默认情况下的node版本 nvm alias default v9.2.1 # 列出可安装的node版本 nvm ls-remote # 列出本地安装了的node版本以及当前正在使用的node版本 nvm ls
另外需要加一句:推荐使用标记为LTS的版本,这种版本官方会用较长的时间进行维护,所谓LTS,就是Long-Term-Support长期支持的意思。
五、强制约束node版本和使用npm还是yarn来进行依赖管理
我们当然可以只通过文件和口头约定的方式来约束大家都使用相同版本的node,并约束大家都使用npm或者yarn其中一种(不可混用)来进行依赖包的管理,但是在书面/口头约定之外,在代码上再进行一下限制会更稳妥一些。下面是我从vue2官方webpack模版的源码里抄过来并按自己的需求修改了一下的两段代码。
限制使用的node和yarn的版本:check-version.js:
const chalk = require('chalk') const semver = require('semver') const packageConfig = require('../package.json') function exec (cmd) { return require('child_process').execSync(cmd).toString().trim() } const versionRequirements = [ { name: 'node', currentVersion: semver.clean(process.version), versionRequirement: packageConfig.engines.node }, { name: 'yarn', currentVersion: exec('yarn --version'), versionRequirement: packageConfig.engines.yarn } ] module.exports = function () { const warnings = [] for (let i = 0; i < versionRequirements.length; i++) { const mod = versionRequirements[i] if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { warnings.push(mod.name + ': ' + chalk.red('当前版本为:' + mod.currentVersion) + ',应为 ' + chalk.green(mod.versionRequirement) ) } } if (warnings.length) { console.log('') console.log(chalk.yellow('要开发本项目, 需要更新/降级以下模块:')) console.log() for (let i = 0; i < warnings.length; i++) { const warning = warnings[i] console.log(' ' + warning) } console.log() process.exit(1) } }
不允许使用npm进行依赖管理(原理是检测项目根目录下是否存在运行npm install时生成的package-lock.json文件,npm-shrinkwrap.json是作向前兼容的,不是很):check-npm.js:
const path = require('path') const chalk = require('chalk') const fse = require('fs-extra') const unnecessaryFiles = [ 'package-lock.json', 'npm-shrinkwrap.json' ] const necessaryFiles = [ 'yarn-lock.json' ] module.exports = async function () { const warnings = [] for (let fileName of unnecessaryFiles) { const targetPath = path.resolve(__dirname, `../${fileName}`) try { const isExisted = await fse.pathExists(targetPath) if (isExisted) { warnings.push( chalk.red(`检测到文件:${fileName}。`) + chalk.green(`请将其删除后使用yarn重新安装依赖。`) ) } } catch (e) { console.log(chalk.red(` 判断文件${targetPath}是否存在时出错了。\n`)) console.log(e) console.log() process.exit(1) } } for (let fileName of necessaryFiles) { const targetPath = path.resolve(__dirname, `../${fileName}`) try { const isExisted = await fse.pathExists(targetPath) if (!isExisted) { warnings.push( chalk.red(`未检测到文件:${fileName}。`) + chalk.green(`请先使用yarn安装项目依赖后再执行其他操作。`) ) } } catch (e) { console.log(chalk.red(` 判断文件${targetPath}是否存在时出错了。\n`)) console.log(e) console.log() process.exit(1) } } if (warnings.length) { console.log('') console.log(chalk.yellow('本项目中请统一使用yarn而非npm进行依赖管理,不要同时混用yarn和npm,这样会带来依赖版本混乱的问题:')) console.log() for (let i = 0; i < warnings.length; i++) { const warning = warnings[i] console.log(' ' + warning) } console.log() process.exit(1) } }
跟check-version.js对应的,需要在package.json文件里添加类似下面这样的代码(这里我认为yarn的版本可以需用限定的太死所以只是限定了一个区间,之所以要求小于2是因为从1都2属于大的版本更新了,现在yarn还没有出2版本,谁知道到时候对应的命令是否还能使用):
{ // ... "engines": { "node": "9.2.1", "yarn": ">= 1.3.2 < 2.0.0" }, // ... }
check-version.js和check-npm.js两个文件的使用方法很简单,在平时开发和打包命令对应的文件的开头,添加类似下面的代码即可(注意导入后的代码后面需要加一个括号去执行它,因为上面两个文件导出的都是函数):
require('./check-versions')() require('./check-npm')() // 如果上面的代码检测到不符合要求的话就会直接关闭进程,便不会执行下面的代码了 // 你的其他代码
最新评论
大哥资深网民啊,01年我还在念小学。。
看着有点难过。。。
嘿嘿,谢谢老哥,也祝老哥事业蒸蒸日上。
我是你唯一的药学类友情链接网站。 作为一个80后的过来人祝福你,生活越来越好。
这篇文章,我们中学那会老师课堂上念给我们听的。
哈哈哈哈哈,没想到啊, 我有手抄版
嗯,是的
好心办坏事多了去啦
哈哈,是的,我15年末来上海写代码了,一晃三年多过去了,好快。
今天看QQ好友的时候突然看到了你的名字,想起几年前在药品国际注册群挺活跃/厉害的你,现在不见踪影了。就搜了一下,没想到你现在转行去写代码了... (刚才打漏了一句话...)