執行某些命令的時候,你是否遇到過提醒版本太低,須要升級版本的提示,那麼對於版本號,是以一個怎樣的規則來進行的限制和匹配的呢? semver, 是一個語義化版本號管理的模塊,能夠實現版本號的解析和比較,規範版本號的格式。node
版本號通常有三個部分,以.
隔開,就像X.Y.Z
,其中react
每一個部分爲整數(>=0),按照遞增的規則改變。git
在修訂版本號的後面能夠加上其餘信息,用-
鏈接,好比:github
在package.json
文件中,咱們所安裝的依賴,都會有版本號的描述,好比使用初始化的一個react工程,在它的package.json
裏自動安裝的依賴npm
"devDependencies": {
"react": "^15.6.1",
"react-dom": "^15.6.1"
}
複製代碼
其實咱們平時看到的版本號,不止有^
前綴的,還有~
,那麼他們表明的含義是什麼呢?json
^
: 容許在不修改[major, minor, patch]中最左非零數字的更改(匹配大於X、Y、Z的更新Y、Z的版本號)數組
在X.Y.Z
結構的版本號中,X、Y、Z都是非負的整數,上面定義的意思就是說從左向右,遇到第一個非零數字是不可修改的,下一個數字能夠更改,好比:bash
^15.6.1"
,最左的非零數字是15
,因此X是不容許更新的,也就是說主版本號不會超過15,表示的就是版本號>=15.6.1 && <16.0.0
^0.1.2
表示版本號>=0.1.2 && < 0.2.0
^0.0.2
,主版本號和次版本號都是0,修訂號爲非零,表示的就是版本號>=0.0.2 && < 0.0.3
~
: 匹配大於X.Y.Z
的更新Z
的版本號dom
~1.2.3
表示版本號>=1.2.3 && < 1.3.0
~0.2.3
表示版本號>=0.2.3 && < 0.3.0
,這種狀況下,~
等價於^
0.0.3
表示版本號>=0.0.3 && < 0.1.0
x
: 能夠替代X、Y、Z中任意一個,表示該位置可更新ui
1.2.x
: >=1.2.0 && < 1.3.0
1.x
: >=1.0.0 && < 2.0.0
*
: 任意版本均可以上面的x
能夠用*
代替,其實,用x
或*
的地方能夠省略不寫,好比1.2.x
和1.2
表示的意思是同樣的
-
:包含第一個版本號和第二個版本號的範圍 表示的是一個閉區間,-
鏈接的兩個版本號範圍都包括
0.1.0
-2
: >=0.1.0 && < 3.0.0
0.1.0
- 2.1.1
: >=0.1.0 && <= 2.1.1
npm install semver
複製代碼
// 引入模塊
const semver = require('semver')
semver.clean(' =v1.1.1 ');// 1.1.1,解析版本號,忽略版本號前面的符號
semver.valid('1.1.1'); // true,版本號是否合法
semver.valid('a.b.c'); // false
semver.satisfies('1.2.4', '1.2.3 - 1.2.5'); // true, 判斷版本是否在某個範圍
複製代碼
這裏只列舉了部分用法,具體的能夠在文檔中查看。
看了semver的源碼,整理了部分方法的實現原理
...
exports.clean = clean;
function clean(version, loose) {
// 替換參數中的空格和符號
var s = parse(version.trim().replace(/^[=v]+/, ''), loose);
return s ? s.version : null;
}
...
複製代碼
...
exports.valid = valid;
function valid(version, loose) {
var v = parse(version, loose);
return v ? v.version : null;
}
...
複製代碼
clean
和valid
都用到了一個方法parse
,這個方法是用來對版本號進行解析檢查是否規範,最後返回一個規範的格式
對版本號的格式進行解析,判斷是否合法,這個方法在不少方法的實現裏面都用到了
exports.parse = parse;
function parse(version, loose) {
if (version instanceof SemVer)
return version;
if (typeof version !== 'string')
return null;
if (version.length > MAX_LENGTH)
return null;
// 是否應用寬鬆模式
var r = loose ? re[LOOSE] : re[FULL];
if (!r.test(version))
return null;
try {
return new SemVer(version, loose);
} catch (er) {
return null;
}
}
/*
* 參數中的loose表示是否寬鬆檢查版本號
* loose爲true的時候,檢查版本號的格式不會那麼嚴格
* 好比定義數字標識符,就定義了一種寬鬆的匹配模式
* /
// ## Numeric Identifier
// A single `0`, or a non-zero digit followed by zero or more digits.
var NUMERICIDENTIFIER = R++;
src[NUMERICIDENTIFIER] = '0|[1-9]\\d*'; // 單個0或者0後面跟着0個或多個不爲0的數字
var NUMERICIDENTIFIERLOOSE = R++;
src[NUMERICIDENTIFIERLOOSE] = '[0-9]+'; // 0-9的1位或多位數字
複製代碼
exports.satisfies = satisfies;
function satisfies(version, range, loose) {
try {
// Range會判斷輸入的範圍是否合法,並返回一個格式化以後的range
range = new Range(range, loose);
} catch (er) {
return false;
}
return range.test(version);
}
複製代碼
satisfies
調用了Range
,用於對用戶輸入的範圍進行規範化
exports.Range = Range;
function Range(range, loose) {
if (range instanceof Range) {
if (range.loose === loose) {
return range;
} else {
return new Range(range.raw, loose);
}
}
if (range instanceof Comparator) {
return new Range(range.value, loose);
}
if (!(this instanceof Range))
return new Range(range, loose);
this.loose = loose;
/*
* 將範圍按照‘||’分開
* 對每一個範圍進行解析,而且過濾出沒有意義的範圍
*/
// First, split based on boolean or ||
this.raw = range;
// 用split將輸入的範圍劃分紅數組
this.set = range.split(/\s*\|\|\s*/).map(function(range) {
// 對數組的每一項進行解析
return this.parseRange(range.trim());
}, this).filter(function(c) {
// throw out any that are not relevant for whatever reason
return c.length;
});
if (!this.set.length) {
throw new TypeError('Invalid SemVer Range: ' + range);
}
this.format();
}
複製代碼
/*
* 對用戶輸入的範圍進行解析檢驗,返回規範的格式
*/
Range.prototype.parseRange = function(range) {
var loose = this.loose;
range = range.trim(); // 去掉先後的空格
debug('range', range, loose);
// 判斷是不是寬鬆模式,並應用‘連字符’的正則去匹配替換
// 將連字符的形式替換成比較符號的形式,`1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4
var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE];
range = range.replace(hr, hyphenReplace);
debug('hyphen replace', range);
// `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace);
debug('comparator trim', range, re[COMPARATORTRIM]);
// `~ 1.2.3` => `~1.2.3`
range = range.replace(re[TILDETRIM], tildeTrimReplace);
// `^ 1.2.3` => `^1.2.3`
range = range.replace(re[CARETTRIM], caretTrimReplace);
// 將表示範圍的字符串的多個空格替換成一個空格
range = range.split(/\s+/).join(' ');
// At this point, the range is completely trimmed and
// ready to be split into comparators.
var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR];
// 將表示範圍的字符串按照空格劃分爲數組,對每個數組向進行解析檢驗,返回規範的表示並從新鏈接成字符串
var set = range.split(' ').map(function(comp) {
return parseComparator(comp, loose);
}).join(' ').split(/\s+/);
if (this.loose) {
// 在寬鬆模式下,過濾掉全部不合法的比較器
set = set.filter(function(comp)
return !!comp.match(compRe);
});
}
set = set.map(function(comp) {
return new Comparator(comp, loose);
});
return set;
};
/**
* 將規範後的範圍字符串從新鏈接起來並返回
*/
Range.prototype.format = function() {
this.range = this.set.map(function(comps) {
return comps.join(' ').trim();
}).join('||').trim();
return this.range;
};
複製代碼