工作中有这么一个场景,需要建立各种各样的项目,每次建立项目的时候我们可以安装一个类似vue-cli 或者 create-react-app 这样的脚手架工具简单的输入一句命令就可以创建出符合要求的工程模板了,这里也来搭建一个小型的脚手架项目。
先贴出项目连接:https://github.com/yangJianWeb/xr-cli
可以直接clone到本地,将命令npm link 全局之后
可配置自己的项目模板,然后新建文件夹将自己的模板clone 到本地。
工程结构
|--bin
| |--xr.js: 脚手架入口文件。
|
|--command
| |--xr-add.js : xr add 命令实现,主要用于向template.json中添加工程模板信息
| |--xr-delete.js: xr del 命令实现,主要用于向template.json中删除工程模板信息
| |--xr-list.js xr ls 命令实现,主要用于展示template.json中已有工程模板信息
| |--xr-init.js xr init命令实现,接受两个必填参数,第一个参数是 通过xr add 添加的模板名称,第二个命令就是项目名称(文件夹名)
|
|--template.json: 用于存储项目模板信息,对象,key为项目模板名称,value为项目远端地址
|
|--node_modules 包依赖
|
|--package.json 包信息
|
需要的工具
- chalk:可以修改控制台输出的字体颜色,变得好看一下,比如
//引入
const chalk = require('chalk');
//使用:绿色成功,红色失败
console.log(chalk.green('success'));
console.log(chalk.red('error'));
- commander: 编写指令和处理命令行的,例如
//引入
const program = require("commander");
// 定义指令
program
.version('1.0.0')
.command('init', 'Generate a new project from a template')
// 解析命令行参数
program.parse(process.argv);
- inquirer:强大的交互式命令行工具
//引入
const inquirer = require('inquirer');
//使用
inquirer
.prompt([
// 一些交互式的问题
])
.then(answers => {
// 回调函数,answers 就是用户输入的内容,是个对象
});
- ora : 好看的加载效果,就是下载的时候会有个转圈圈的效果
//引入
const ora = require('ora')
//使用
let spinner = ora('downloading template ...')
spinner.start()
入口文件
新建一个文件夹,这里命名为xr-cli文件夹,然后 npm init 初始化一些信息。再以此安装上述所需要用到的各种npm 包。
下载完之后,在项目根路径下新建bin文件夹,文件夹下新建xr.js命令行入口文件。
并在第一行(注意是固定的第一行)加上
#!/usr/bin/env node
这个主要作用是让系统看到这一行的时候,会沿着该路径去查找 node 并执行,
主要是为了兼容 Mac ,确保可执行。bin 目录初始化
新建完之后引入commander工具包,在这个入口文件中会定义四个命令,分别是命令行的新增、删除、展示、生成项目模板
函数引入
// 引入 commander 包
const program = require('commander');
//引入add、del、list、init 命令所需要运行的函数,四个文件分别在根目录下的command文件夹下
const xrAdd = require('../command/xr-add');
const xrDelete = require('../command/xr-delete');
const xrList = require('../command/xr-list');
const xrInit = require('../command/xr-init');
定义指令
基本的指令前缀是是xr(该文件的文件名)即在终端输入的都是 xr 打头,后面追加参数。根据 xr 后面不同的参数来运行不同的命令。定义四个指令
program
.version(require('../package').version) // 定义当前版本
.usage('<command> [options]') // 定义使用方法
program.command('add') // 新增命令
.description('Add a new template') // 命令描述
.alias('a') // 命令别名
.action(() => { // 触发该命令时,运行的函数
xrAdd();
})
program.command('delete')
.description('Delete a template')
.alias('del')
.action(() => {
xrDelete();
})
program.command('list')
.description('List all the templates')
.alias('ls')
.action(() => {
xrList();
})
program.command('init')
.description('Generate a new project')
.alias('i')
.action(() => {
xrInit();
})
program.parse(process.argv) // 解析命令行参数
// 最后加上这一句,用于提供帮助信息
//输入xr的时候会出现基本的 -V -h的友好提示
if (!program.args.length) {
program.help()
}
至此入口文件就编写好了,可以看到在入口文件中就是定义并引入了各个子命令的方法。
然后我们可以分不同的命令不同的文件去维护。这样比较清晰。方便维护和开发。
也可以在终端中直接运行(先注释还未介绍引入的 add/delete/list/init 四个命令)
node ./bin/xr
可以查看到效果如下:
在界面上回出现比较友好的命令行的提示
也可以在package.json中注册bin命令,这样就可以直接跑注册的命令就好
"bin": {
"xr": "bin/xr"
}
然后在项目根目录下执行 sudo npm link(把命令挂载到全局的意思),输入密码,就会显示如下界面
这样每次只要输入 xr,就可以直接运行了,其作用和上述输入 node ./bin/xr 效果一样。
与之相对应的解绑命令是 sudo npm unlink
定义子命令
先在项目的根目录下新建一个 template.json 文件,里面的内容就是一个 {}。用于存储增加的项目地址。
xr 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`)
// 自定义交互式命令行的问题及简单的校验
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
}
}
]
//对外暴露的方法。
module.exports = () => {
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('')
console.log(chalk.green('Added successfully!'))
console.log(chalk.grey('The latest template list is:'))
console.log(tplObj)
console.log('')
})
})
}
在控制台输入
xr add
可以看到有我们预设的一些交互式命令,提示用户输入模板名称,模板对应的地址
也可以去项目根目录下查看template.json文件内容,发现已近多了我们刚刚出入的内容
这里预设的是模板名称是必填,如果不填直接回车,也会检验,提示用户必填该信息,如下:
xr delete
xadd 是在 template.json文件中增加模板信息,与之对应的是xr 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
}
}
}]
module.exports = () => {
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('')
console.log(chalk.green('Deleted successfully!'))
console.log(chalk.grey('The latest template list is: '))
console.log(tplObj)
console.log('')
})
})
}
先看在template.json中有两条模板信息,接下来我们就用这个命令删除其中的test模板信息
运行下 xr delete试试
可以看到已近删除成功了。在这里,删除的模板名称也是必填的,如果不输入直接回车就会验证报错,或者模板名称没有的话也会显示相应的信息
xr list
这个应该是所有命令中最简单的了,就是单纯的用来展示template.json文件中有多少模板信息
#!/usr/bin/env node
const tplObj = require(`${__dirname}/../template`)
module.exports = () => {
console.log(tplObj)
}
控制台运行一下 xr list 或者 (xr ls)
可以看到目前template.json中就只有一条模板信息
xr init
最重要的一步了。前面的 xr add、xr delete、xr list都是在对template.json文件进行读写操作,用于存储一些项目模板名称,以及该项目对应的远端地址。在 xr 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`)
const exec = require('child_process').exec
// 下面这两句不能放在 module.exports 方法中,不然会循环很多次,导致内存溢出
program.usage('<template-name> [project-name]')
program.parse(process.argv)
module.exports = () => {
//当没有输入参数的时候给个提示
if (program.args.length < 1) return program.help()
// 好比 vue init webpack project-name 的命令一样,第一个参数是 webpack,第二个参数是 project-name
// 第一个参数是 git clone template.json中的那个模板,第二个命令就是项目名称(文件夹名)
let templateName = program.args[0]
let projectName = program.args[1]
console.log(['templateName', templateName])
console.log(['projectName', projectName])
// 校验一下参数
if (!tplObj[templateName]) {
console.log(chalk.red('Template does not exit!'))
return
}
if (!projectName) {
console.log(chalk.red('Project should not be empty!'))
return
}
const url = tplObj[templateName];
// 执行下载方法并传入参数
let cmdStr = `git clone ${url} ${projectName}`;
console.log(chalk.green(`clone project from ${url}`));
console.log(chalk.green('\n Start generating...'))
// 出现加载图标
const spinner = ora("Downloading...");
spinner.start();
exec(cmdStr, (error, stdout, stderr) => {
if (error) {
console.log(error)
process.exit()
}
console.log(chalk.green('\n √ Generation completed!'))
console.log(`\n cd ${projectName} && npm install \n`)
process.exit()
})
}
这里给大家完整的演示一下,在之前我先 xr add 了一条模板信息名称为 tmp,对应的映射路径是gitlab上的一个模板信息地址。
之后在我本地磁盘上任何一个地方新建一个test文件夹用于测试可以发现,成功将远端的项目git clone 下来了
至此,一个小型自检的cli脚手架工具就完成了。