# 简述
作为前端,相信大家都用过vue-cli
或者create-react-app
,输入命令,自动完成,接下来就会介绍怎么完成自己的脚手架。
# 构建准备
# Inquirer.js
用于用户与命令行交互,做一个脚手架或者在某些时候要与用户进行交互, 需要用到它。
# 参数
type :
String
表示提问的类型,默认input,包括:input, confirm, list, rawlist, expand, checkbox, password, editorname :
String
存储当前问题回答的变量message :
String
问题的描述default :
String
默认值choices :
Array|Function
列表选项validate :
Function
对用户的回答进行校验filter :
Function
对用户的回答进行过滤,返回处理后的值transformer :
Function
对用户回答的显示效果进行处理(如:修改回答的字体或背景颜色),不会影响最终的答案的内容when :
Function, Boolean
根据前面问题的回答,判断当前问题是否需要被回答pageSize :
Number
修改某些type
类型下的渲染行数prefix :
String
修改message
默认前缀suffix :
String
修改message
默认后缀askAnswered :
Boolean
如果答案已经存在,则强制提示该问题loop :
Boolean
启用列表循环
# 安装
npm install inquirer
# 使用
const inquirer = require('inquirer');
const list = [
{
type: 'input',
message: '请输入手机号:',
name: 'phone',
validate: function(val) {
if(val.match(/\d{11}/g)) { // 校验位数
return val;
}
return "请输入11位数字";
}
},
{
type: "confirm",
message: "是否使用监听?",
name: "watch",
prefix: "前缀"
},
{
type: 'list',
message: '请选择一种水果:',
name: 'fruit',
choices: [
"Apple",
"Pear",
"Banana"
],
filter: function (val) { // 使用filter将回答变为小写
return val.toLowerCase();
}
}
]
inquirer
.prompt(list)
.then(answers => {
// Use user feedback for... whatever!!
})
.catch(error => {
if(error.isTtyError) {
// Prompt couldn't be rendered in the current environment
} else {
// Something else when wrong
}
});
# commander.js
完整的 node.js
命令行解决方案
# 安装
npm install commander
# 版本号
const program = require('commander');
program
.version('1.0.0')
.parse(process.argv);
或者自定义flag
const program = require('commander');
program
.version('1.0.0', '-v, --version')
.parse(process.argv)
# option方法
# 参数说明
自定义flag<必须>
一长一短的flag,中间可以逗号、竖线或空格隔开
flag后面可以跟参数,<>定义必需参数,[]定义可选参数
选项的描述<可省略>:在使用-h或者--help时会显示
选项的默认值<可省略>
# 定义多个选项
const program = require('commander');
program
.version('1.0.0')
.option('-a, --add', 'add Something')
.option('-u, --update', 'update Something')
.option('-r, --remove', 'remove Something')
.parse(process.argv);
短flag使用-,长flag使用--
# command
# 参数
- 自定义命令名称
- 名称<必须>
- 命令参数<可选>:
- <>和[]定义参数
- 命令的最后一个参数可以是可变的,需要在数组后面加入 ...,在命令后面传入的参数会被传入到 action 的回调函数以及 program.args 数组中
- 命令描述<可省略>
- 配置选项<可省略>:可配置noHelp、isDefault等
const program = require('commander');
program
.version('1.0.0')
.command('my-cli <path>')
.option('-a, --add <fileName>', 'add a file')
.option('-u, --update <fileName>', 'update a file')
.option('-r, --remove <fileName>', 'remove a file')
.action(function(path, sh) {
// action 定义命令的回调函数
console.log(path)
console.log(sh.add)
})
program.parse(process.argv);
# description
const program = require('commander');
program
.version('1.0.0')
.description('It is my cli')
.parse(process.argv);
# parse 方法
用于解析process.argv,设置options以及触发commands
# 其他依赖
也会用到这些依赖
# 项目文件结构搭建
1.建立一个文件夹
2.在建好的文件下下面执行npm init
, 在package.json
中添加
"chalk": "^1.1.3",
"commander": "^2.9.0",
"download-git-repo": "^1.1.0",
"inquirer": "^6.2.0",
"ora": "^3.0.0",
"request": "^2.88.0"
npm i
安装依赖
- 建立bin文件夹,在bin文件夹里面分别建
tem,tem-init,tem-add,tem-list,tem-delete
- bin
- tem
- tem-add
- tem-list
- tem-delete
- tem-init
# tem文件
#!/usr/bin/env node // 表示执行程序是node
const program = require('commander')
// 定义当前版本
// 定义使用方法
// 定义四个指令
program
.version(require('../package').version)
.usage('<command> [options]')
.command('add', 'add a new template')
.command('delete', 'delete a template')
.command('list', 'list all the templates')
.command('init', 'generate a new project from a template')
// 解析命令行参数
program.parse(process.argv)
tem
定义了四个命令
# tem-add
添加模版
#!/usr/bin/env node
// 交互式命令行
const inquirer = require('inquirer')
// 修改控制台字符串的样式
const chalk = require('chalk')
// node 内置文件模块
const fs = require('fs')
// 读取根目录下的 template.json
const tplObj = require(`${__dirname}/../template.json`)
// 自定义交互式命令行的问题及简单的校验
let question = [
{
name: "name",
type: 'input',
message: "请输入模板名称",
validate (val) {
if (val === '') {
return 'Name is required!'
} else if (tplObj[val]) {
return 'Template has already existed!'
} else {
return true
}
}
},
{
name: "url",
type: 'input',
message: "请输入模板地址",
validate (val) {
if (val === '') return 'The url is required!'
return true
}
}
]
inquirer
.prompt(question).then(answers => {
// answers 就是用户输入的内容,是个对象
let { name, url } = answers;
// 过滤 unicode 字符
tplObj[name] = url.replace(/[\u0000-\u0019]/g, '')
// 把模板信息写入 template.json 文件中
fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(tplObj), 'utf-8', err => {
if (err) console.log(err)
console.log('\n')
console.log(chalk.green('Added successfully!\n'))
console.log(chalk.grey('The latest template list is: \n'))
console.log(tplObj)
console.log('\n')
})
})
# tem-list
这个比较简单,获取模版
#!/usr/bin/env node
const tplObj = require(`${__dirname}/../template`)
console.log(tplObj)
# tem-delete
删除某个模版
#!/usr/bin/env node
const inquirer = require('inquirer')
const chalk = require('chalk')
const fs = require('fs')
const tplObj = require(`${__dirname}/../template`)
let question = [
{
name: "name",
message: "请输入要删除的模板名称",
validate (val) {
if (val === '') {
return 'Name is required!'
} else if (!tplObj[val]) {
return 'Template does not exist!'
} else {
return true
}
}
}
]
inquirer
.prompt(question).then(answers => {
let { name } = answers;
delete tplObj[name]
// 更新 template.json 文件
fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(tplObj), 'utf-8', err => {
if (err) console.log(err)
console.log('\n')
console.log(chalk.green('Deleted successfully!\n'))
console.log(chalk.grey('The latest template list is: \n'))
console.log(tplObj)
console.log('\n')
})
})
# tem-init
初始化项目
#!/usr/bin/env node
const program = require('commander')
const chalk = require('chalk')
const ora = require('ora')
const download = require('download-git-repo')
const tplObj = require(`${__dirname}/../template`)
program
.usage('<template-name> [project-name]')
program.parse(process.argv)
// 当没有输入参数的时候给个提示
if (program.args.length < 1) return program.help()
// 好比 vue init webpack project-name 的命令一样,第一个参数是 webpack,第二个参数是 project-name
let templateName = program.args[0]
let projectName = program.args[1]
// 小小校验一下参数
if (!tplObj[templateName]) {
console.log(chalk.red('\n Template does not exit! \n '))
return
}
if (!projectName) {
console.log(chalk.red('\n Project should not be empty! \n '))
return
}
url = tplObj[templateName]
console.log(chalk.white('\n Start generating... \n'))
// 出现加载图标
const spinner = ora("Downloading...");
spinner.start();
// 执行下载方法并传入参数
download (
url,
projectName,
err => {
if (err) {
spinner.fail();
console.log(chalk.red(`Generation failed. ${err}`))
return
}
// 结束加载图标
spinner.succeed();
console.log(chalk.green('\n Generation completed!'))
console.log('\n To get started')
console.log(`\n cd ${projectName} \n`)
}
)
# template.json
{"vue-spa-template": "hbbaly/vue-spa-template"}
模版地址就是download-git-repo
要读取的git地址,这个地址 不需要完整的, 只需要域名后面的就行,当然也可以写完整的地址,如果是公司搭建的git服务就不能简写了
# package.json
{
"name": "vue-cli-tem",
"version": "1.0.1",
"description": "这个是一个简单的脚手架",
"preferGlobal": true,
"bin": {
"tem": "./bin/tem",
"tem-add": "bin/tem-add",
"tem-delete": "bin/tem-delete",
"tem-list": "bin/tem-list",
"tem-init": "bin/tem-init"
},
"dependencies": {
"chalk": "^1.1.3",
"commander": "^2.9.0",
"download-git-repo": "^1.1.0",
"inquirer": "^6.2.0",
"ora": "^3.0.0",
"request": "^2.88.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"cli",
"vue",
"hbbaly"
],
"author": "hbbaly",
"license": "MIT"
}
# 上传到npm
在根目录建立.npmignore,这个和.gitignore一样,我们在.npmignore添加 /node_modules即可,上传到npm时候,会忽略这些文件。
假设已经有npm账号
执行npm login
登陆之后,npm publish
# 验证
npm i vue-cli-item -g
依次执行这几个命令