脚手架开发必知必会,这篇文章介绍了本来开发脚手架的流程,以及命令行输入命令如何查找并执行对应的脚手架。
以下这篇文章带你更进一步了解脚手架如何解析命令参数从而执行对应的复杂命令的。市面上yargs, commander
这两个库是比较流行的,下面我们一起来看看吧。
yargs
API介绍
js
#!/usr/bin/env node
const yargs = require("yargs")
// 解析参数
const {hideBin} = require("yargs/helpers")
/**
* ./src/index.js zh llm
* [ 'zh', 'llm' ]
*/
// process.argv 中前两个元素分别是: 当前node程序文件位置,当前终端执行文件夹(pwd)
const argv = hideBin(process.argv) // 解析参数,只保留控制台输入的参数
const cli = yargs(arg)
// 处理命令提示
cli
.usage("Usage: $0 <command> [options]") // 脚手架通用模板格式。显示在开头位置 $0表示脚手架名称
.argv

如果不指定严格模式,输入错误的命令将没有任何错误反馈。
js
// 严格模式。让输入不正确的命令或者参数出现错误提示。而不是没有任何反馈。
.strict()

约束最少输入的命令,不包括主命令
js
// 约束最少输入的命令
.demandCommand(1, "A command is required. Pass --help to see all available commands and options.")

配置option别名
js
// 别名
.alias("h", "help")
.alias("v", "version")


修改终端信息展示宽度,让其有更好看的样式。cli.terminalWidth()
表示整个终端宽度。
js
// 修改终端信息展示宽度
.wrap(cli.terminalWidth())

help中定义结尾内容。
js
.epilogue(`
When a command fails, all logs are written to lerna-debug.log in the current working directory.
For more information, check out the docs at <工具官网>
`)
增加全局的option选项,对所有命令都生效。
js
// 配置全局option(配置多个)
.options({
debug: {
type: "boolean",
describe: "dev debug",
default: true,
alias: "d",
// 表示隐藏命令,用于内部开发使用。他不会显示在options列表中,但是可以被使用。
hidden: true
}
})
// 配置全局option(配置一个)
.option("name", {
type: "string",
describe: "project name",
alias: "n"
})
进行options分组。
js
.group(["debug", "name"], "dev options")
.group(['version', 'help'], "global options")

定义子命令。两种方式
- 命令格式
- 描述
- builder 定义当前命令options
- handler 指定当前命令对应的脚本
js
// 方式一
.command({
command: "init [name]", // 命令名 option
aliases: ["i"], // 可配置多个别名
describe: "List local packages",
// 执行这个命令之前回调, 比如定义当前命令参数
builder(yargs) {
yargs.option("name", {
type: "string",
describe: "a project name",
// 脚手架中的别名不能重复
alias: "n1"
})
},
// 执行当前命令脚本
handler(argv) {
console.log("argv", argv)
},
})
// 方式二
.command('serve [port]', 'start the server', (yargs) => {
return yargs
.(positional / option)('port', {
describe: 'port to bind on',
default: 5000
})
}, (argv) => {
if (argv.verbose) console.info(`start server on :${argv.port}`)
serve(argv.port)
})
错误命令名称,给出错误提示,匹配相近的命令提示用户。 会打印help信息,然后给出一个建议。我们也可以通过fail
来定义自己的建议内容给。
js
// 输入错误命令,提示用户
.recommendCommands()
// 定制自己的错误提示信息
.fail((msg, err) => {
console.log(msg)
})


向参数列表中注入参数。我们不再通过hideBin
来解析参数。
js
const { version } = require("../package.json")
const context = {
yargsStudy: version
}
const cli = yargs()
cli
.parse(process.argv.slice(2), context)

完整的yargs demo代码
js
#!/usr/bin/env node
const yargs = require("yargs")
// 解析参数
// const {hideBin} = require("yargs/helpers")
// 移除文本首行空格
// const dedent = require("dedent")
/**
* ./src/index.js zh llm
* [ 'zh', 'llm' ]
*/
// const arg = hideBin(process.argv) // 解析参数,只保留控制台输入的参数
// const cli = yargs(arg)
const { version } = require("../package.json")
const context = {
yargsStudy: version
}
const cli = yargs()
cli
// 脚手架通用模板格式。$0表示脚手架名称
.usage("Usage: $0 <command> [options]")
// 输入错误命令,提示用户
.recommendCommands()
// 定制自己的错误提示信息
.fail((msg, err) => {
console.log(msg)
})
// 严格模式。让输入不正确的命令或者参数出现错误提示。而不是没有任何反馈。
.strict()
// 约束最少输入的命令
.demandCommand(1, "A command is required. Pass --help to see all available commands and options.")
// 别名
.alias("h", "help")
.alias("v", "version")
// 修改终端信息展示宽度
.wrap(cli.terminalWidth())
// 结尾输出内容
.epilogue(`
When a command fails, all logs are written to lerna-debug.log in the current working directory.
For more information, check out the docs at https://lerna.js.org/docs/introduction
`)
// 配置全局option(配置多个)
.options({
debug: {
type: "boolean",
describe: "dev debug",
default: true,
alias: "d",
// 表示隐藏命令,用于内部开发使用
// hidden: true
}
})
// 配置全局option(配置一个)
.option("name", {
type: "string",
describe: "project name",
alias: "n"
})
.group(["debug", "name"], "dev options")
.group(['version', 'help'], "global options")
.command({
command: "init [name]",
aliases: ["i"],
describe: "List local packages",
// 执行这个命令之前回调,一般定义一些options
builder(yargs) {
yargs.option("name", {
type: "string",
describe: "a project name",
// 别名不能重复
alias: "n1"
})
},
handler(argv) {
console.log("argv", argv)
},
})
// 定义命令 方式二
.command('serve [port]', 'start the server', (yargs) => {
return yargs
.positional('port', {
describe: 'port to bind on',
default: 5000
})
}, (argv) => {
if (argv.verbose) console.info(`start server on :${argv.port}`)
// serve(argv.port)
})
// .argv
.parse(process.argv.slice(2), context)
总结
Yargs常用API
- Yargs.usage(提示脚手架用法)
- Yargs.strict(开启以后可以报错提示)
- Yargs.demandCommand(规定最少传几个command)
- Yargs.recommendCommands(在输入错误command以后可以给你推荐最接近的正确的command)
- Yargs.alias(起别名)
- Yargs.options(定义多个option)
- Yargs.option(定义option)
- Yargs.fail(错误处理方法)
- Yargs.group(给option分组)
- Yargs.wrap(命令行工具的宽度)
- Yargs.epilogue(命令行工具底部的提示)
Yargs开发流程
- 脚手架初始化(将process.argv当参数传递给Yargs())
- 脚手架命令注册(Yargs.command)
- 脚手架参数解析(Yargs.parse)
commander
API介绍
commander提供类和单例(Command, program
)来供我们调用
js
const { program } = require('commander');
// 或者
const { Command } = require('commander');
const program = new Command();
处理命令提示,如果直接指定那么它将会把当前脚手架入口文件名连接上, 所以我们应该通过name()
修改其名称。
js
// 错误
.usage(`${Object.keys(config.bin)[0]} command [options]`)
// 正确
.name(Object.keys(config.bin)[0])
.usage(`command [options]`)

通过
version()
指定版本号
js
.version(config.version)
// 或者传入详细信息
program.version('0.0.1', '-v, --vers', '选项描述');
使用.option()
方法来定义选项
选项及其选项参数可以用空格分隔,也可以组合成同一个参数。选项参数可以直接跟在短选项之后,也可以在长选项后面加上 =
。
js
serve -p 80
serve -p80
serve --port 80
serve --port=80
解析后的选项可以通过Command
对象上的.opts()
方法获取,同时会被传递给命令处理函数。
选项可以设置必填,可以通过[]
来指定选项参数是否可选(如果指定就必须进行传入),并且可以指定默认值(使用时在不指定选项的时候生效)。
js
program
// 必传选项
.requiredOption("-r --require <params>", "必传选项")
// 选项可选参数,通过[]定义
.option("-z --zh [params]", "描述选项", "默认值")
program.parse();
通过
.command()
或.addCommand()
可以配置命令。
js
// 返回新生成的命令(即该子命令)以供继续配置,并不返回program
program
// 指定必填参数和可选参数
.command('clone <source> [destination]')
.description('命令描述')
.option("-s -sonOption <reason>", "命令选项描述")
.action((source, destination, options) => {
console.log('当前命令执行需逻辑', source, destination, options);
});

我们可以通过
addCommand
注册子命令的子命令,类似于命令分组。
js
const serve = new Command("serve")
serve.command("start <port>")
.description("启动服务")
.action((port) => {
console.log("启动服务", port);
});
program.addCommand(serve)
并且我们可以通过
arguments
设置通用的匹配命令,处理通过command, addCommand
定义的命令外,其他命令都会命中arguments
的定义。
js
program
.arguments("<cmd> <options>")
.description("通用命令描述")
.action((cmd, options) => {
console.log("通用命令执行逻辑", cmd, options);
});
当
.command()
带有描述参数时,就意味着使用独立的可执行文件作为子命令, 即组合命令。 在控制台中声明command-test install
时,其实是查找command-test-install
脚手架。所以我们可以设置executableFile
来修改执行的脚手架。
js
program
.command("currentInstall", "联合命令")
.command('install [name]', '联合命令,关联vu-cli脚手架', { executableFile: 'vue-cli' })
.command('list', '设置isDefault指定脚手架默认命令, 并且隐藏命令', { isDefault: true, hidden: true })
program.parse(process.argv);



完整配置
js
#! /usr/bin/env node
const {program, Command} = require("commander")
const config = require( "../package.json")
program
.name(Object.keys(config.bin)[0])
.usage(`command [options]`)
.version(config.version)
// .usage(`${Object.keys(config.bin)[0]} command [options]`)
// .requiredOption("-r --require <params>", "必传选项")
.option("-l --llm <params>", "描述选项", "默认值")
.option("-z --zh [params]", "描述选项", "默认值")
// 子命令
program
// 指定必填参数和可选参数
.command('clone <source> [destination]')
.description('命令描述')
.option("-s --sonOption <reason>", "命令选项描述")
.action((source, destination, options) => {
console.log('当前命令执行需逻辑', source, destination, options);
});
// 分组命令
const serve = new Command("serve")
serve.command("start <port>")
.description("启动服务")
.action((port) => {
console.log("启动服务", port);
});
program.addCommand(serve)
// 通用命令
program
.arguments("<cmd> <options>")
.description("通用命令描述")
.action((cmd, options) => {
console.log("通用命令执行逻辑", cmd, options);
});
// 关联命令
program
.command("currentInstall", "联合命令")
.command('install [name]', '联合命令,关联vu-cli脚手架', { executableFile: 'vue-cli' })
.command('list', '设置isDefault指定脚手架默认命令', { isDefault: true, hidden: true })
console.log("options", program.opts())
program.parse(process.argv)
yargs和commander区别
- yargs
- 采用链式方法调用设计(如
.command().option().action()
连续调用) - 更灵活,支持多种配置方式
- 采用链式方法调用设计(如
- commander
- 注册子命令时会返回新的命令实例(不通过链式返回自身)
- 更结构化,强调清晰的命令分层(
addCommand
)
往期年度总结
- 在上海的忙碌一年,依旧充满憧憬(2024)
- 四年沿海城市,刚毕业,一年3家公司
- 七月仿佛又回到了那一年(2023年中总结)
- 一位初入职场前端仔的年度终结 <回顾2022,展望2023>
- 大学两年半的前端学习
往期文章
- 告别后端,浏览器端也可以轻松操作文件了?
- 文本选区有啥魔力
- 前端可以知道的录制浏览器标签页,没有黑魔法
- 一个关联本地页面镜像的功能,我了解到这些
- 啊,原来sessionStorage是这样的
- 如何像掘金编辑器一样粘贴图片即可上传服务器
- Nest装饰器全解析
- Nest世界中的AOP
- Nestjs如何解析http传输的数据
- 如何理解js的DOM事件系统
- 半年没看vue官网,3.5刚刚发布,趁机整理下
- 啊,你还在找一款强大的表格组件吗?
- 前端大量数据层级展示及搜索定位预览
- 如何从0开始认识m3u8(提取,解析及下载)
- 展示大量数据节点(tree),引发的一次性能排查
- ts装饰器的那点东西
- 这是你所知道的ts类型断言和类型守卫吗?
- TypeScript官网内容解读
- 经常使用ts的你,知道这些内容?
- 你有了解过原生css的scope?
- 现在比较常用的移动端调试你知道哪些?
- 众多跨标签页通信方式,你知道哪些?(二)
- 众多跨标签页通信方式,你知道哪些?
- 反调试吗?如何监听devtools的打开与关闭
- 因为原生,选择一家公司(前端如何防笔试作弊)
- 结合开发,带你熟悉package.json与tsconfig.json配置
- 如何优雅的在项目中使用echarts
- 如何优雅的做项目国际化
- 近三个月的排错,原来的憧憬消失喽
- 带你从0开始了解vue3核心(运行时)
- 带你从0开始了解vue3核心(computed, watch)
- 带你从0开始了解vue3核心(响应式)
- 3w+字的后台管理通用功能解决方案送给你
- 入职之前,狂补技术,4w字的前端技术解决方案送给你(vue3 + vite )
专栏文章
🔥如果此文对你有帮助的话,欢迎💗关注 、👍点赞 、⭐收藏 、✍️评论, 支持一下博主~
公众号:全栈追逐者,不定期的更新内容,关注不错过哦!