【青训营】写一个简单的cli

249 阅读1分钟

cli 功能说明

  • 初始化项目,自动下载github模板资源,并且自动安装
  • 依赖安装后自动启动模板仓库
  • 实现约定式路由

依赖安装

$ yarn add commander download-git-repo ora handlebars figlet clear chalk open watch -D
  • 依赖说明:

    • commander Nodejs 命令配置工具
    • inquirer 一组常用的交互式命令行用户界面。
    • download-git-repo 下载 github 资料的工具
    • ora 一个优雅的终端加载器
    • handlebars 一个简单的模板语言
    • figlet 在终端中打印好看的自定义提示语的工具
    • clear 终端清屏工具,功能等同 linux 命令 clear ,windows 的 cls
    • chalk 类似终端的画笔工具,可以给 log 不同颜色样式的提示语
    • open 用于自动打开浏览器
    • watch 用于监听文件变化

初始化

  • 新建文件夹: ddb

  • 进入该文件夹,使用 npm 初始化:npm init -y

  • 新建相关文件:

    • 新建 /bin/ddb.js
    • 新建 /lib/init.js
    • 新建 /lib/download.js
    • 新建 lib/refresh.js
  • 配置 package.json

{
  "name": "my-cli",
  "version": "1.0.0",
  "description": "",
  "main": "kkb.js",
  "bin": {
    "my-cli": "./bin/kkb.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
复制代码
  • 绑定软连接:npm link

开始实现

获得版本号

  • /bin/kkb.js 中编写代码如下
#!/usr/bin/env node
'use strict';

const program = require('commander')

// 策略模式
program.version(require('../package').version)
program.parse(process.argv)
复制代码
  • 在终端输入命令:my-cli -V,是否显示对应的版本号,如果有,则说明前面我们的配置都是成功的

打印初始化提示语

  • 编写 /lib/init.js 文件如下:
const figlet = promisify(require('figlet')) 
const clear =require('clear')
const chalk = require('chalk')
const log = content => console.log(chalk.green(content))
module.exports = async name => {
    clear()
    const data = await figlet('my-cli welcome', {
        font: 'Ghost',
        horizontalLayout: 'default',
        verticalLayout: 'default',
        width: 80,
        whitespaceBreak: true
    })
    log(data)
}
  • 修改 /bin/ddb.js 代码
#!/usr/bin/env node
'use strict';

const program = require('commander')

// 策略模式
program.version(require('../package').version)
program.command('init <name>')
    .description('init project')
    .action(
        require('../lib/init')
    )
    
program.parse(process.argv)
复制代码
  • 测试 ,在终端输入命令 my-cli init demo ,我们可以看到终端输出的很大的 my-cli welcome 字样

  • 接下来,我们继续编写 init 方法,它主要包括以下几个功能

    • download 下载 选中的 模板信息
    • install 为该模板安装依赖
    • open 安装依赖后自动打开浏览器预览
  • 下载 选中的模板信息,编写 /lib/download.js 文件

const { promisify } = require("util")
const ora = require('ora')
module.exports.clone = async function(repo,desc) {
    const download = promisify(require("download-git-repo"))
    const process = ora(`正在下载...${repo}`)
    process.start();
    await download(repo, desc);
    process.succeed();
}

注:如果ora报错,降低ora版本到5

  • 修改/lib/init.js 文件

const {clone} = require('./download')

module.exports = async name => {
    //...
    //下载项目模版
    log('🚀创建项目'+ name)
    await clone('github:su37josephxia/vue-template',name) 
    //...
}
  • 然后进行依赖的安装,还是在/lib/init.js 文件中进行修改
require('child_process')
const ora = require('ora')

const spawn =async(...args) => {
    // 同步 Promise api
    // 输出流 子进程流合并到主进程
    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 => {
    //...
    //下载依赖 npm i
    //子进程
    const installProc = ora(`🏃安装依赖...`)
    installProc.start();
    await spawn('npm',['install'],{cwd:`./${name}`})
    installProc.succeed();
    log(chalk.green(`
    👌安装完成
    To get Start:
    ===================
    cd ${name}
    npm run serve
    ===================
    `))
}
  • 接下来,我们实现 自动打开浏览器和 启动项目,编辑 /lib/init.js
const open = require('open')
//...
module.exports = async name => {
    //...
    open('http://localhost:8080')
    await spawn('npm',['run','serve'],{cwd:`./${name}`})
}

至此,我们就已经实现了上面的需求啦,接下来,我们来实现路由约定式路由配置

约定式路由配置

在 vue 开发过程中,我们有一个操作是必定会重复的,我们可以使用 命令的方式来实现:

  • 新增一个页面
  • 配置路由信息
  • 其他页面添加这个新页面
//读文件列表
//拼代码 模版渲染读方式
const fs = require('fs')
const handlebars = require('handlebars')
const chalk = require('chalk')
module.exports = async () => {
    //获取列表
    const list = fs.readdirSync('./src/views')
    .filter((v)=> v!=='Home.vue')
    .map((v) => ({
        name: v.replace(".vue","").toLowerCase(),
        file: v
    }))
    //生成路由定义
    compile({list},'./src/router.js','./template/router.js.hbs')

    // 生成菜单
  compile({list}, './src/App.vue', './template/App.vue.hbs')

  /**
   * 模板编译
   * @param {*} meta 数据定义
   * @param {*} filePath 目标文件
   * @param {*} templatePath 模板文件
   */


    function compile(meta,filePath,templatePath){
        if(fs.existsSync(templatePath)){
            const content = fs.readFileSync(templatePath).toString();
            const result = handlebars.compile(content)(meta);
            fs.writeFileSync(filePath,result);
            console.log(chalk.green(`🚀${filePath}创建成功`))
        }
    }
}
  • /lib/init.js 加入
program
  .command("refresh")
  .description("refresh routers and menu")
  .action(require('../lib/refresh'))
复制代码
  • 测试:

    • 模板文件/src/views/ 目录下新建 Test.vue ,内容为
    <template>
      <div class="test">
        <h1>This is an Test page</h1>
      </div>
    </template>
    复制代码
    
    • 执行 ddb refresh 后,即可看到成功提示,查看 模板文件/src/routers.js模板文件/src/App.vue ,即可看到新增的 Test 相关路由和 tab
    • 界面 localhost:8080 也可以看到对应的效果

到这里,手写 cli 就完成啦

参考资料: