CLI
命令行界面(command-line inteface),缩写CLI,对于我们大前端来说,cli的使用已经屡见不鲜了,如框架三巨头的Angular CLI、create-react-app、Vue CLI,每一个都坐拥成千上万的死忠。
需求
当我们创建一个新的项目的时候,往往是用三大佬的脚手架生成新的项目再进行开发,但是这样的问题是初始化模版都是官方自己维护的,关于我们经常使用的代码片段、发布配置、layout布局等等都得重新搞,(vue cli 可以使用 --preset参数指定模版路径, react的没有,angular没有用过,暂时不知道)。
流程
流程大概如下,接下来开始我们的撸码之旅。

向大佬问好
我们创建个项目,来实现超级简单的一个cli,获取用户输入姓名,并向大佬问好。
我们的cli叫rollins-cli,为什么叫rollins, 因为Seth Rollins啊, 前the shield成员。

Burn it down!
好了,继续写我们的代码
创建node项目三板斧
mkdir rollins-cli
cd $_
npm init -y
touch index.js, 创建index.js,写入以下内容
#! /usr/bin/env node
const readline = require('readline')
/**
* @param { String } input 显示的文案
* @returns { Promise } Promise对象
*/
const getCommandParams = input => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
return new Promise((resolve, reject) => {
rl.question(input, data => {
rl.close()
resolve(data)
})
})
}
const getUserInput = (input= '来了,老弟,你叫啥?\n') => {
getCommandParams(input).then(data => {
data ? console.log(`原来是${data} 大佬,失敬失敬`) : getUserInput('你不说我就一直问\n你叫啥?\n')
})
}
getUserInput()
运行效果如下

万里长征第一步,以上代码我们就已经实现了一个简易的CLI。
bin字段
在package.json文件中添加bin字段, bin字段用来指定各个内部命令对应的可执行文件的位置。
{
"bin": {
"rollins": "./bin/index.js"
}
}
上面代码说明rollins命令的执行文件在bin文件夹下的index.js
# 创建bin目录
mkdir bin
# 将index.js文件移动到bin目录下
mv index.js ./bin
执行npm link命令

会将定义的rollins字段拷贝到全局安装的node_modules文件夹内,并创建一个软连接。

在全局node的bin目录中同样也创建了软连接

然后我们就可以在全局中使用rollins这个命令了。

同样是完美运行,接下来让我们招兵买马继续我们的革命之旅。
摇人吧
执行npm i commander inquirer chalk ora -S,来壮大我们的势力。
接下来,依次介绍登场的各位豪杰
commander
commander 是一个node命令行的解决方案,可以简化我们的开发流程。
demo
#! /usr/bin/env node
const program = require('commander')
program
.version('0.0.1')
.command('init')
.alias('i')
.description('初始化')
.option('-a, --name <name>', '参数介绍', '我是默认值')
.action(option => {
console.log('init命令被调用')
})
program.parse(process.argv)
// 输入为空时 显示帮助信息
if(!program.args.length){
program.help()
}
简单说下上面命令
version版本command定义命令alias定义命令的简写description命令描述option传递给命令的参数-a参数名简写--name参数名<name>或者[name]<>表示强制用户输入[]表示可选参数 用户没有输入使用默认值或者undefined
action命令触发时候的回调
当我们什么命令也没有输入的时候,会触发帮助信息,我们直接调用grogram.help()函数即可,grogram会根据我们定义的命令自动生成帮助信息

更多关于grogram的内容,请去官网查看,这点不多的东西,我们就够用了。
inquirer
inquirer 给用户提供了一个漂亮的界面和提出问题流的方式。

demo
#! /usr/bin/env node
const { prompt } = require('inquirer')
const promptList = [
{
type: 'input',
name: 'name',
message: '请输入文件夹名',
filter: function(val) {
return val.trim()
},
validate: function(val) {
return !!val.trim() ? true : '请输入名称'
}
},
{
type: 'list',
name: 'template',
message: '请选择系统',
choices: [
'Mac',
'windows',
'Linux'
]
}
]
prompt(promptList).then(params => {
console.log(params)
})

chalk
chalk是命令行美化的模块,并且支持模板输出。
demo
#! /usr/bin/env node
const chalk = require('chalk')
console.log(`${chalk.yellow(`${process.cwd()}`)}`)
console.log(`
${chalk.cyan(`
${chalk.gray('$')} cd test
${chalk.gray('$')} npm install
${chalk.gray('$')} npm start
`)}
`)
输出如下

download-git-repo
download-git-repo是支持从git仓库中下载源码的库,细化到分支级别,支持GitHub, GitLab, Bitbucket。
api
download(repository, destination, options, callback)
- repository 仓库地址
- destination 项目保存地址
- options 可选参数
{ clone: false }默认为fasle 表示是否使用git clone,默认使用 http download 方式进行下载
- callback 回调函数
demo
以github为例,重点说下repository这个参数,仓库地址是我们的用户名/项目名

代码如下
const download = require('download-git-repo')
download('Ortonzhang/template-dva', './test', (err) => {
err ? console.log('err is: %s', err) : console.log('success')
})
整合
首先创建我们的模版,使用umi和dva创建好项目后,发送到github, 也可以基于别的框架进行创建项目。

创建template.json, 写入模版地址
{
"umi": "Ortonzhang/template-umi",
"dva": "Ortonzhang/template-dva"
}
将上面我们的demo整合起来,完成脚手架的全部代码。
#! /usr/bin/env node
const program = require('commander')
const { prompt } = require('inquirer')
const chalk = require('chalk')
const download = require('download-git-repo')
const fs = require('fs')
// 用来实现loading效果
const ora = require('ora')
const { promisify } = require('util');
const path = require('path')
// 使用 promisify 设置成promise
const readFileAsync = promisify(fs.readFile);
const writeFileAsync = promisify(fs.writeFile)
// 模版配置文件
const templateConfig = require(`${__dirname}/../template`)
// 获取当前设备的路径分割符号
const sep = path.sep
// 设置问题
const promptList = [
{
type: 'input',
name: 'name',
message: '请输入文件夹名',
filter: function(val) {
return val.trim()
},
validate: function(val) {
return !!val.trim() ? true : '请输入名称'
}
},
{
type: 'list',
name: 'template',
message: '请选择模版',
choices: [
'dva',
'umi'
]
}
]
const create = async () => {
// 获取用户输入 拿到项目名和模版
let { name, template } = await prompt(promptList)
// 判断Linux和windows 设置不同的分隔符
let step = sep === '/' ? '/' : '\\'
console.log(`
${chalk.white(`✨ Creating project in ${chalk.yellow(`${process.cwd()}${step}${name}`)}.`)}
`)
const spinner = ora('🗃 Initializing git repository...');
spinner.start()
// 下载模版
await downloadPromise(`${templateConfig[template]}`, `${name}`)
// 获取模版的package.json文件数据
let stringPackage = await readFileAsync(`./${name}/package.json`, {encoding: 'utf8'})
// 转化成json
let jsonPackage = JSON.parse(stringPackage)
// 设置名称
jsonPackage.name = `${name}`
// 创建新的json数据
const updateJson = JSON.stringify(jsonPackage, null, 2)
// 写入新的package.json
await writeFileAsync(`./${name}/package.json`, updateJson)
spinner.stop();
console.log(`
${chalk.green(`🎉 Successfully created project ${chalk.yellow(`${name}`)}.`)}
${chalk.white('👉 Get started with the following commands:')}
${chalk.cyan(`
${chalk.gray('$')} cd ${name}
${chalk.gray('$')} npm install
${chalk.gray('$')} npm start
`)}
`);
}
/**
* 下载模版
* @param {String} path 模版路径
* @param {String} name 项目名称
* @return {Promise} promise对象
*/
const downloadPromise = (path, name) => {
return new Promise((resolve, reject) => {
download(path, name, (err) => {
if(err){
console.log(chalk.red(err))
reject()
}
resolve()
})
})
}
program
// 版本号
.version(require('../package').version)
// 设置命令
.command('create')
//设置别名
.alias('c')
// 描述
.description('创建新项目')
// 动作
.action(option => {
create()
})
program.parse(process.argv)
// 输入为空时 显示帮助信息
if(!program.args.length){
program.help()
}
以上就是rollins-cli的全部代码,发布到npm上就可以了。
发布
执行npm login, 登陆npm输入用户名密码
执行npm publish,进行发布操作。
常见错误
- 镜像地址
- 当前镜像地址是淘宝的话,就会导致失败,执行
npm config set registry https://registry.npmjs.org/修改npm的registry
- 当前镜像地址是淘宝的话,就会导致失败,执行
- 版本号重复
- 修改package.json里面的version字段
- 没有权限
- 执行
npm adduser,输入用户名、密码、邮箱
- 执行
参考
总结
以上内容,我们就编写发布了一个属于自己的npm包。麻雀虽小,五脏倒也全,本文代码请戳github。