搭建属于自己的脚手架
cli是一种通过命令行来交互的工具应用,比较常见的有create-react-app、vue-cli等,它们都是可以将一段js脚本,通过封装为可执行代码的形式,进行一些操作。
使用cli之后,我们能够快速创建出一些我们业务中的模板文件,比如快速创建一个项目内容,配置一些公共的eslint、webpack等工具,最方便的地方在于cli能够提供一些交互式的命令,根据用户回答的结果可以动态地更改从而渲染对应的模板文件。
准备阶段:
脚手架的基本工作流程如下:
- 通过命令行交互方式询问用户相应问题
- 根据用户的回答结果生成对应的文件
在搭建过程中我们主要依赖以下工具库:
commander | inquirer | chalk | download-git-repo
我们首先创建一个文件夹cli,并进入,接着npm init创建package.json文件,在cli目录下创建cli.js文件,此时的目录结构如下:
cli
├─ cli.js
└─ package.json
进入cli.js文件,在文件开头输入:
cli.js文件中
#! /usr/bin/env node
这句代码必须加上,主要是为了让系统看到这句话的时候会沿着该路径去查找node并执行
console.log('cli is working')
完成之后我们进入package.json文件中,将name改为"clay-cli",新增"bin":"cli.js",接着在命令行输入npm link,将命令挂载到全局,这样每次我们输入cli,就可以直接运行了。
package.json文件中
{
"name": "clay-cli",
"version": "1.0.0",
"description": "",
"main": "cli.js",
"bin":"cli.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
命令行结果
PS F:\clayCli\cli> npm link
npm WARN clay-cli@1.0.0 No description
npm WARN clay-cli@1.0.0 No repository field.
up to date in 0.35s
C:\Program Files\nodejs\clay-cli -> C:\Program Files\nodejs\node_modules\clay-cli\cli.js
C:\Program Files\nodejs\node_modules\clay-cli -> F:\clayCli\cli
PS F:\clayCli\cli> clay-cli
cli is working
PS F:\clayCli\cli>
做完这些我们就可以开始使用以下的工具库了
1.inquirer(该工具主要作用是通过命令行询问用户问题,记录回答结果)
首先npm install inquirer --save,接着进入cli.js文件中将其引入
#! /usr/bin/env node
const inquirer = require('inquirer')
const promptList = [{
type:'list',
message:'请选择一种框架',
name:'framework',
choices:[
'vue2.0',
'vue3.0',
'react'
]
}]
inquirer.prompt(promptList).then(answers=>{
console.log(answers);
})
由于交互的问题种类不同,inquirer为每个问题提供了很多参数,包括:
- type:提问的类型,包括:input,confirm,list,rawlist,expand,checkbox,password,editor
- name: 存储当前问题回答的变量(对应上述例子中answers中key值)
- message:描述问题
- default:当没选择或者没填写时的默认值
- choices:顾名思义,选项列表
- validate:校验用户的回答
- filter:对用户的回答进行过滤
- when:根据前面的问题,进行判断 下面举一些常见的例子:
1) type:list 见上
2) type:input
const promptList = [
{
type:'input',
message:'请输入手机号',
name:'phonenum',
validate:function(val){
if(val.match(/\d{11}/g)){
return val
}else{
return '请输入11位数字'
}
}
}
]
inquirer.prompt(promptList).then(answers=>{
console.log(answers);
})
3) type:confirm
const promptList = [
{
type:'confirm',
message:'是否使用vue',
name:'usevue',
}
]
inquirer.prompt(promptList).then(answers=>{
console.log(answers);
})
4) type:checkbox
const promptList = [
{
type:'checkbox',
message:'选择模块',
name:'modules',
choices:[
"ts",
"eslint",
"webpack"
]
}
]
inquirer.prompt(promptList).then(answers=>{
console.log(answers);
})
2.commander(该工具主要作用是可以自定义命令行指令)
commander的常用API:
- command:自定义执行的命令
- option:定义可选参数
- description:描述命令
- action:命令执行完成后所执行的方法
- parse:解析命令行参数(注意:这个方法一定要在最后的时候调用)
现在我们修改cli.js文件,npm install commander --save,接着引入commander
cli.js文件
const program = require('commander')
program
.command('create <appname>') //其中<>代表必填 []代表可选
.option('-f,--force','描述性文字')
.description('创建一个新的项目')
.action((name,options)=>{
console.log('项目名是:',name,'options:',options);
})
program.parse()
上图可以看到:
1.当你输入clay-cli时,会提示所有可输入命令
2.可以通过描述文字判断该命令的作用
3.通过输入-f,可以获得options对象,对象的key就是force,value为true,可以通过判断force值存在与否进行下一步的操作(注意:option根据需要你想设置为啥就是啥,不用非得是-f,--force)
commander结合inquiry一起使用
#! /usr/bin/env node
const inquirer = require('inquirer')
const program = require('commander')
program
.command('create <appname>')
.description('创建一个新的项目')
.option('-f,--force','描述性文字')
.action(name=>{
return inquirer.prompt([
{
type:'list',
name:'framework',
message:'请选择一个框架',
choices:[
'vue',
'react'
]
}
])
.then(answers=>{
console.log(name,'选择的框架是:', answers);
})
})
program.parse()
3.chalk(该工具主要作用是美化控制台输出的内容)
chalk是一个颜色的插件,可以更改命令行的颜色,npm install chalk --save
const program = require('commander')
const chalk = require('chalk')
program
.command('create <appname>') //其中<>代表必填 []代表可选
.option('-f,--force','描述性文字')
.description('创建一个新的项目')
.action((name,options)=>{
console.log('项目名是:',chalk.red(name));
console.log('项目名是:',chalk.blue(name));
console.log('项目名是:',chalk.yellow(name));
})
program.parse()
4.download-git-repo(该工具主要作用是下载远程模板)
完成用户的选项之后就可以使用download-git-repo可以从github上面下载模板文件,但是download-git-repo是不支持promise的,在使用它的时候需要使用util模板中的promisify方法对其进行promise化
download(repository, destination, options, callback),其中repository是下载地址,destination为下载目录,这两个参数是最主要的
整合搭建
接下来我们利用上述四个工具库来搭建完整的脚手架
1.利用commander创建命令
打开cli.js文件,创建create 命令,过程如上述commander例子
cli.js文件
const program = require('commander')
program
.command('create <appname>') //其中<>代表必填 []代表可选
.option('-f,--force','描述性文字')
.description('创建一个新的项目')
.action((name,options)=>{
console.log('项目名是:',name,'options:',options);
})
program.parse(process.argv)// 解析用户执行命令传入参数
2.接着创建lib文件夹,并在lib下创建create.js文件,写入代码并在cli.js文件中引入
create.js文件
module.exports = async function(name,options){}
cli.js文件
const program = require('commander')
program
.command('create <appname>') //其中<>代表必填 []代表可选
.option('-f,--force','描述性文字')
.description('创建一个新的项目')
.action((name,options)=>{
require('./lib/create.js')(name,options)
})
program.parse(process.argv)
运行结果:
3.在github中配置模板文件
首先进入github点击右上角Organizations,进行一番配置后创建两个repositories,一个为vue-template-1.0,创建好之后再创建两个tag,分别为v1.0,v2.0;另一个为vue-template-3.0,创建好之后再创建4个tag,分别为v1.0.0、v2.0.0、v3.0.0、v3.1.2。再将对应代码传上去。模板我已上传至github(地址)(注:本文所用模板引自文章 从 0 构建自己的脚手架/CLI知识体系(万字))
4.获取模板信息
在lib目录下新建http.js文件,使用github提供的接口获取数据
http.js文件
const axios = require('axios')
axios.interceptors.response.use(res => {
return res.data
})
async function getRepoList(){
return axios.get('https://api.github.com/orgs/clay-cli/repos')
}
async function getTagList(repo) {
return axios.get(`https://api.github.com/repos/clay-cli/${repo}/tags`)
}
module.exports = {
getRepoList,
getTagList
}
5.用户选择模板
打开create.js文件,调用http.js文件中的接口函数获取模板列表,接着使用inquirer引入模板列表
create.js文件
const {getRepoList,getTagList} = require('./http.js')
const inquirer = require('inquirer')
async function getRepos){
const repoList = await getRepoList()
const repos = repoList.map(item=>item.name)
const {repo} = await inquirer.prompt([{
name:'repo',
type:'list',
choices:repos,
message:'请选择一个模板'
}])
return repo
}
module.exports = async function(name,options){
const repo = await getRepos)
console.log('用户选择的模板是',repo);
}
6.用户选择版本
逻辑同选择模板
const {getRepoList,getTagList} = require('./http.js')
const inquirer = require('inquirer')
async function getRepos(){
const repoList = await getRepoList()
const repos = repoList.map(item=>item.name)
const {repo} = await inquirer.prompt([{
name:'repo',
type:'list',
choices:repos,
message:'请选择一个模板'
}])
return repo
}
async function getTags(repo){
const tagList = await getTagList(repo)
const tags = tagList.map(item=>item.name)
const {tag} = await inquirer.prompt([{
name:'tag',
type:'list',
choices:tags,
message:'请选择一个版本'
}])
return tag
}
module.exports = async function(name,options){
const repo = await getRepos()
const tag = await getTags(repo)
console.log('用户选择的模板是',repo,'用户选择的版本是',tag);
}
7.下载模板
完成以上工作后我们就可以使用download-git-repo远程下载模板了
1)reate.js文件中引入,因为其是不支持promise的,需要先进行promise化。
2) 获取当前命令行选择的目录
3) 新增需要创建的目录地址,即模板所在地址
4) 创建下载地址
5)模板下载完成后使用chalk进行提示
const {getRepoList,getTagList} = require('./http.js');
const inquirer = require('inquirer');
const path = require('path');
const util = require('util');
const downloadGitRepo = require('download-git-repo');
const chalk = require('chalk');
//获取模板
async function getRepos(){
const repoList = await getRepoList();
const repos = repoList.map(item=>item.name);
const {repo} = await inquirer.prompt([{
name:'repo',
type:'list',
choices:repos,
message:'请选择一个模板'
}])
return repo
}
//获取标签
async function getTags(repo){
const tagList = await getTagList(repo);
const tags = tagList.map(item=>item.name);
const {tag} = await inquirer.prompt([{
name:'tag',
type:'list',
choices:tags,
message:'请选择一个版本'
}])
return tag;
}
//下载
async function onDownload(name,repo,tag){
const requestUrl = `clay-cli/${repo}${tag?'#'+tag:''}`;//创建下载地址
const cwd = process.cwd(); //获取当前命令行选择的目录
const targetPath = path.join(cwd,name); //模板下载所在地址
const downloadFunc = util.promisify(downloadGitRepo);
downloadFunc(requestUrl,targetPath);
}
module.exports = async function(name,options){
const repo = await getRepos();
const tag = await getTags(repo);
await onDownload(name,repo,tag);
console.log(`\r\n成功创建项目 ${chalk.cyan(name)}`);
console.log(`\r\n cd ${chalk.cyan(name)}`);
console.log(' npm run dev\r\n');
}
最终的目录结构
clayCli
├─ cli
│ ├─ clay
│ │ └─ vue3.0-template
│ │ ├─ babel.config.js
│ │ ├─ package.json
│ │ ├─ public
│ │ │ ├─ favicon.ico
│ │ │ └─ index.html
│ │ ├─ README.md
│ │ └─ src
│ │ ├─ App.vue
│ │ ├─ assets
│ │ │ └─ logo.png
│ │ ├─ components
│ │ │ └─ HelloWorld.vue
│ │ └─ main.js
│ ├─ cli.js
│ ├─ lib
│ │ ├─ create.js
│ │ └─ http.js
│ ├─ package-lock.json
│ └─ package.json
└─ README.md
参考文章
从 0 构建自己的脚手架/CLI知识体系(万字),作者:ITEM (写的非常非常好,想学习cli的一定要拜读)