为什么固定版本号?
-
为了安全。新版本有可能被黑客植入病毒。
如何看待 NPM 包 event-stream 被黑客篡改,发现包含恶意代码? -
保证功能一致性。
一般情况下,限定了major和minor版本,Library对外暴露的API是向下兼容的,但有时候作者会修复Bug或者添加新功能,很可能影响原有功能的一致性,甚至产生新的Bug。 -
降低维护成本。
虽然有package-lock.json这个文件,可以显式地锁定依赖包的版本,但它本身是不可靠的,且易变。
一旦Library的作者发布了新版本,或者项目开发者自己使用了npm update命令,package-lock.json又会发生变化。
如果我们依赖这种包管理的方式,在看代码PR时,还要观察这个文件的变更,成本太高。
有风险的做法
有几种写法?分别代表什么意思?看一下官方的介绍 https://docs.npmjs.com/files/package.json#dependencies
以下版本号写法,都是有风险的:
-
插入符号(^),限定major版本:
功能兼容,旧的API不会消失,但可能会有新的API。
如"@koa/cors": "^2.2.3",
实际安装的版本会是 version >=2.2.3 && version < 3.0.0 -
波浪符号(~),限定minor版本:
功能几乎一样,API不变,可能会有小问题修复和改进。
如"@koa/cors": "~2.2.3",
实际安装的版本会是 version >=2.2.3 && version < 2.3.0
解决办法
可以用以下命令查看安装的依赖:
npm list --depth=0
以下代码可以检查当前安装的包的版本,并固化版本号。如果对脚本不放心,那就用npm list 命令吧。
自动修改package.json,会通过npm list --depth=0,找到本地实际安装的依赖包版本号,替换掉package.json中的范围版本号。
const process = require('child_process');
const fs = require('fs');
const path = require('path');
const packageFilePath = path.resolve('package.json');
let packageContentStr = fs.readFileSync(packageFilePath, 'utf-8');
function fixPackagesVersion() {
const packageContent = JSON.parse(packageContentStr);
replaceDepsVersion(packageContent.dependencies);
replaceDepsVersion(packageContent.devDependencies);
}
function replaceDepsVersion(dependencies) {
Object.keys(dependencies).forEach(function (packageName) {
const originalVerStr = `"${packageName}"\\s*:\\s*".*"`;
const packageStartReg = new RegExp(originalVerStr);
const finalVer = getFinalVersion(packageName);
if (finalVer) {
const stableVersionStr = `"${packageName}": "${finalVer}"`;
packageContentStr = packageContentStr.replace(packageStartReg, stableVersionStr);
}
});
}
function getFinalVersion(packageName) {
const str = actualInstalledPackages;
const regStr = `──\\s${packageName}@.*`;
const reg = new RegExp(regStr);
const packageAndVerResult = str.match(reg);
if (packageAndVerResult && packageAndVerResult.length) {
const value = packageAndVerResult[0];
return value.substring(value.lastIndexOf('@')+1);
} else {
return '';
}
}
const command = 'npm list --depth=0';
let actualInstalledPackages;
process.exec(command, (err, stdout, stderr) => {
actualInstalledPackages = stdout;
fixPackagesVersion();
fs.writeFileSync(packageFilePath, packageContentStr, 'utf-8');
});