mri@1.2.0源码阅读
mri(全称 Minimalist CLI Argument Parser)的核心作用是解析命令行传入的参数,把用户在终端输入的命令行参数(比如 --port 3000、-v)转换成易于在代码中使用的 JavaScript 对象。
有什么特点?
- 超轻量:体积只有几百 KB,无依赖,适合对包体积敏感的 Node.js/ 前端工具开发;
- 易用性:API 极简,几行代码就能完成参数解析;
- 常用场景:前端工程化工具(如自定义构建脚本、本地开发服务器、CLI 工具)中解析命令行参数。
index文件
mri-1.2.0/src/index.js
export default function (args, opts) {
args = args || []; // 命令行参数数组(比如 process.argv.slice(2))
opts = opts || {}; // 解析配置(别名、默认值、类型等)
var k, arr, arg, name, val, out={ _:[] }; // out 解析结果对象
var i=0, j=0, idx=0, len=args.length;
// 标记是否配置了别名、严格模式、默认值
const alibi = opts.alias !== void 0;
const strict = opts.unknown !== void 0;
const defaults = opts.default !== void 0;
opts.alias = opts.alias || {};
opts.string = toArr(opts.string); // 字符串类型的参数名数组
opts.boolean = toArr(opts.boolean); // 布尔类型的参数名数组
// 别名双向绑定
if (alibi) {
for (k in opts.alias) {
arr = opts.alias[k] = toArr(opts.alias[k]);
for (i=0; i < arr.length; i++) {
(opts.alias[arr[i]] = arr.concat(k)).splice(i, 1);
}
}
}
// 布尔 / 字符串类型补全
// 布尔类型:把别名也加入布尔列表
for (i=opts.boolean.length; i-- > 0;) {
arr = opts.alias[opts.boolean[i]] || [];
for (j=arr.length; j-- > 0;) opts.boolean.push(arr[j]);
}
// 字符串类型:同理,把别名也加入字符串列表
for (i=opts.string.length; i-- > 0;) {
arr = opts.alias[opts.string[i]] || [];
for (j=arr.length; j-- > 0;) opts.string.push(arr[j]);
}
// 默认值关联类型
if (defaults) {
for (k in opts.default) {
name = typeof opts.default[k];
arr = opts.alias[k] = opts.alias[k] || [];
if (opts[name] !== void 0) {
opts[name].push(k);
for (i=0; i < arr.length; i++) {
opts[name].push(arr[i]);
}
}
}
}
// 遍历解析命令行参数
const keys = strict ? Object.keys(opts.alias) : [];
for (i=0; i < len; i++) {
arg = args[i]; // 当前处理的参数(比如 '--port'、'-p=3000')
// 1. 遇到 "--" 终止解析,后续参数全部放入 out._
if (arg === '--') {
out._ = out._.concat(args.slice(++i));
break;
}
// 2. 计算参数前的 "-" 数量(比如 --port 是 2 个,-p 是 1 个)
for (j=0; j < arg.length; j++) {
if (arg.charCodeAt(j) !== 45) break; // "-"
}
// 3. 无 "-":非选项参数,放入 out._
if (j === 0) {
out._.push(arg);
// 4. 以 "no-" 开头(比如 --no-open):布尔值取反
} else if (arg.substring(j, j + 3) === 'no-') {
name = arg.substring(j + 3);
if (strict && !~keys.indexOf(name)) {
return opts.unknown(arg);
}
out[name] = false;
// 5. 正常选项参数(比如 --port=3000、-p3000、-p 3000)
} else {
// 找到 "=" 的位置(分割参数名和值)
for (idx=j+1; idx < arg.length; idx++) {
if (arg.charCodeAt(idx) === 61) break; // "="
}
// 取值:有=则取=后的值;无=则取下一个参数(如果下一个不是-开头)
name = arg.substring(j, idx);
val = arg.substring(++idx) || (i+1 === len || (''+args[i+1]).charCodeAt(0) === 45 || args[++i]);
arr = (j === 2 ? [name] : name);
// 遍历解析(比如 -pv 会拆成 p 和 v 分别处理)
for (idx=0; idx < arr.length; idx++) {
name = arr[idx];
if (strict && !~keys.indexOf(name)) return opts.unknown('-'.repeat(j) + name);
// 把解析后的值存入 out(toVal 是核心工具函数)
toVal(out, name, (idx + 1 < arr.length) || val, opts);
}
}
}
// 1. 补全默认值:如果参数未解析到,用默认值填充
if (defaults) {
for (k in opts.default) {
if (out[k] === void 0) {
out[k] = opts.default[k];
}
}
}
// 2. 同步别名:比如解析到 -p=3000,自动给 out.port 也赋值 3000
if (alibi) {
for (k in out) {
arr = opts.alias[k] || [];
while (arr.length > 0) {
out[arr.shift()] = out[k];
}
}
}
return out;
}
toArr
function toArr(any) {
return any == null ? [] : Array.isArray(any) ? any : [any];
}
toVal
function toVal(out, key, val, opts) {
var x,
old = out[key],
nxt = !!~opts.string.indexOf(key)
? val == null || val === true
? ''
: String(val)
: typeof val === 'boolean'
? val
: !!~opts.boolean.indexOf(key)
? val === 'false'
? false
: val === 'true' ||
(out._.push(((x = +val), x * 0 === 0) ? x : val), !!val)
: ((x = +val), x * 0 === 0)
? x
: val;
out[key] =
old == null ? nxt : Array.isArray(old) ? old.concat(nxt) : [old, nxt];
}
示例
const mri = require('mri');
const args = mri(process.argv.slice(2));
console.log(args);