怎样实现一个CLI小工具

735 阅读2分钟

首先,新建一个vue-auto-router-cli文件夹,cd vue-auto-router-cli然后执行npm init -y初始化一个项目,再把所需要的依赖安装一下


mkdir vue-auto-router-cli

cd vue-auto-router-cli

npm init -y

npm i commander download-git-repo ora handlebars clear chalk open watch -s

在生成的package.json中添加

 "bin": {
    "rjd": "./bin/index.js"
  },

修改后的package.json文件

//package.json
{
  "name": "vue-auto-router-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "rjd": "./bin/index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

新建bin文件夹,添加index.js,在文件头添加

#!/usr/bin/env node

终端执行npm link将rjd命令绑定,是否绑定成功可以通过在终端执行rjd命令来看index.js(可以在index.js文件中console一下)文件是否执行。

#!/usr/bin/env node

// console.log('cli....');

// 终端工具依赖
const program = require('commander');

// 版本设定,这里取自package.json中的version
program.version(require('../package.json').version);

// rjd init test初始化一个项目
program
  .command('init <name>')
  .description('init project')
  .action(require('../lib/init.js'));

// rjd refresh刷新路由和菜单
program
  .command('refresh')
  .description('refresh routers and menu ')
  .action(require('../lib/refresh.js'));

// 解析命令行参数,注意这个方法一定要放到最后调用
program.parse(process.argv);

新建lib文件夹,并新建download.js, init.js文件

// download.js
/*
 * @Descripttion: 下载
 * @Author: RJD
 * @Date: 2020-06-29 15:18:22
 * @LastEditors: RJD
 * @LastEditTime: 2020-06-30 10:55:10
 */ 
const { promisify } = require('util');

module.exports.clone = async (repo, desc) => {
  // 从git上下载东西
  const download = promisify(require('download-git-repo'));
  // ora包用于显示加载中的效果
  const ora = require('ora');
  const process = ora(`下载......${repo}`);
  // 开始加载
  process.start();
  // 下载资源
  await download(repo, desc);
  // 加载成功
  process.succeed();
}
// init.js
/*
 * @Descripttion: 初始化
 * @Author: RJD
 * @Date: 2020-06-29 16:28:32
 * @LastEditors: RJD
 * @LastEditTime: 2020-06-30 15:21:21
 */ 
// node中自带api
const { promisify } = require('util');
const figlet = promisify(require('figlet'));
// 清屏
const clear = require('clear');
// 彩色
const chalk = require('chalk');
//修改日志为彩色
const log = content => console.log(chalk.green(content));
// 下载
const { clone } = require('./download');
// 自动打开浏览器
const open = require('open');

const spawn = async (...args) => {
  const { spawn } = require('child_process');
  return new Promise(resolve => {
    const proc = spawn(...args);
    proc.stdout.pipe(process.stdout);
    proc.stderr.pipe(process.stderr);
    proc.on('close', () => {
      resolve();
    })
  })
}

module.exports = async name => {
  // 打印欢迎页面
  // 清除终端输出
  clear()
  const data = await figlet('RJD WELCOME');
  // 加载一个欢迎效果
  log(data);

  // clone
  log(`创建项目: ${name}`);
  await clone('github:su37josephxia/vue-template', name);

  // 自动安装依赖
  log('安装依赖');
  await spawn('cnpm', ['install'], {cwd: `./${name}`});
  log(`
安装完成:
To get start:
=====================
 cd ${name}
 npm run serve
=====================
`);
  // 在默认浏览器中打开http://localhost:8080
  open('http://localhost:8080');
  // 启动
  await spawn('npm', ['run', 'serve'], {cwd: `./${name}`});
}

在lib下新建refresh.js文件

/*
 * @Descripttion: 
 * @Author: RJD
 * @Date: 2020-06-30 10:39:43
 * @LastEditors: RJD
 * @LastEditTime: 2020-06-30 15:20:49
 */ 
// 读取文件所需依赖包
const fs = require('fs');
// 模板工具
const handlebars = require('handlebars');
// const chalk = require('chalk');

module.exports = async () => {
  // 获取views文件夹下的文件列表
  const list = fs.readdirSync('./src/views')
    .filter(v => v !== 'Home.vue')
    .map(v => ({
      name: v.replace('.vue', '').toLowerCase(),
      file: v
    }))
    console.log(list);
    
    // 生成路由定义
    compile({list}, './src/router.js', './template/router.js.hbs');
    
    // 生成菜单
    compile({list}, './src/App.vue', './template/App.vue.hbs');
    
    /**
     * @name: 模版编译
     * @param {*} meta 数据定义
     * @param {*} filePath 目标文件
     * @param {*} templatePath 模板文件
     * @return: 
     */
    function compile(meta, filePath, templatePath) {
      // 先判断模板路径是否存在
      if(fs.existsSync(templatePath)){
        // 读取模板路径下的文件,然后toString将二进制流转换为可读文本
        const content = fs.readFileSync(templatePath).toString();
        // 通过handlebars进行编译
        const result = handlebars.compile(content)(meta);
        // 利用fs.writeFileSync将结果写入filePath
        fs.writeFileSync(filePath, result);
        console.log(`${filePath} 创建成功`);
      }
    }
}

另附模板文件

App.vue.hbs

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> 
      {{#each list}}
      | <router-link to="/{{name}}">{{name}}</router-link>
      {{/each}}
    </div>
    <router-view/>
  </div>
</template>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

router.js.hbs

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {{#each list}}
    {
      path: '/{{name}}',
      name: '{{name}}',
      component: () => import('./views/{{file}}')
    },
    {{/each}}
  ]
})

至此,在终端执行rjd init test会自动下载项目文件,然后自动执行cnpm install安装项目依赖

rjd init test

在views文件中添加*.vue文件后终端执行rjd refresh自动添加router-link和router

rjd refresh

例如在views中新建一个Contact.vue文件,然后执行rjd refresh.

生成的router.js

生成的App.vue文件

一个简单的cli工具就完成啦,哈哈哈