搭建文件

mkdir vue-auto-router-cli
cd vue-auto-router-cli
npm init -y
npm i commander download-git-repo ora handlebars figlet clear chalk open -s
  1. download-git-repo 作用是从传入的连接中下载并提取一个 git 存储库(如:GitHub,GitLab,Bitbucket),更详细的在这
  2. ora 可以理解为 loading 吧
  3. handlebars 可以理解为将数据和视图分离的一个插件,
  4. figlet 使你的 log 更炫酷?!
  5. clear 清里 cmd 面板了。
  6. chalk 例子:chalk.blue(‘Hello world!’);明白作用了吧。
  7. open 字面意思。

文件目录

vue-auto-router-cli
├──bin #程序执行入口
├────mycli.js #整个程序入口文件
├──node_modules 
├──package-lock.json 
├──package.json

mycli.js

#!/usr/bin/env node 
// 以sheel 脚本的形式来执行
// 执行解释器类型是node。使用node去执行
// 程序执行入口bin
// 整个程序入口文件mycli

console.log('cli.....')

修改package.json

  "bin":{
    "mycli" : "./bin/mycli.js"
  },
npm link

在项目目录下执行,相当于把入口文件链接到全局
当在其他文件下执行,mycli 这个指令,都会打印出来./bin/mycli.js文件里面的内容

mycli.js

// 定制命令行界面的介绍
// 比如我们执行 vue cli 的时候,出来的命令行界面

const program = require('commander')
// -v 时候 的版本号。我们直接用package上的version字段
program.version(require('../package.json').version)

// 创建,命令,命令行定制 
// <name> 代表参数
// 策略模式开发
program
    .command('init <name>')
    .description('init project') //描写
    .action(name=>{ // 做的事情
      console.log('init'+ name)
      }
    )
  program.parse(process.argv)// 现在进程的argv,最后一行是固定的

WX20200727-161525@2x.png

打印process.argv的结果
process.argv [ '/Users/chensi/nvm/versions/node/v8.16.0/bin/node',
  '/Users/chensi/nvm/versions/node/v8.16.0/bin/mycli',
  'init',
  'abc' ]

实现init函数的功能

下载代码
下载依赖

目录结构
根目录下:

├──lib 
├────download.js 
├────init.js 

lib/download.js

`const {promisify} =require('util')`
// 实现一个clone函数,远程去拉取
// 是一个异步函数
module.exports.clone = async function (repo,desc) {
  // repo 从哪拉
  // desc 拉到哪里去
  const download =  promisify(require('download-git-repo'))

  //进度条
  const ora = require('ora')
  // 进度条内容
  const process = ora(`下载....${repo}`)
  // 开启进度条
  process.start()
  // 开始下载
  await download(repo,desc)
  //显示完成
  process.succeed()
}

lib/init.js

// 打印欢迎界面
const {clone} =require('./download')
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))
module.exports = async name=>{
  //清屏幕
  clear()
  // 打印
  const data= await figlet('mycli-ohh Welocome')
  log(data)
}

bin/mycli.js

修改命令的执行结果
 .action(require('../lib/init')

WX20200727-171929@2x.png

下载代码

bin/mycli.js

下载vue框架
module.exports = async name=>{
  //清屏幕
  clear()
  // 打印
  const data= await figlet('mycli-ohh Welocome')
  log(data)
  // 克隆
  log('🚀创建项目:'+name) //  name是传进来的参数,创建的项目名称
  // 当我们去执行 myclie init abc 时,相当于把项目克隆下来,放在abc文件里面
  // 别人在vue-cli的工具上,又添加了渲染模板;如果是真是的项目开发,可以直接去vue的git地址拉取
  await clone('https://github.com/vuejs/vue-cli.git', name)
}

在执行

mycli init abc

WX20200802-120040@2x.png

下载成功
在目录下创建了abc这个文件

WX20200802-120204@2x.png

安装依赖

安装依赖,就是执行npm install 方法
使用spawn 方法:通过生成一个子进程,去执行指定的命令

child_process.spawn(command[, args][, options])
http://nodejs.cn/api/child_process.html#child_process_child_process_spawn_command_args_options

// 打印欢迎界面
const {clone} =require('./download')
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))

// -- 改造spawn 
// ------两步操作:
// --------- 1.对接输出流
// --------- 2.promisify,变成promise函数,会方便些,原生的promisify是一个回调函数
const spawn  =async(...args)=>{
  // 原生的spawn 
  const {spawn} = require('child_process')
  // 封装 promise 方法;返回执行承诺
  return new Promise(resolve=>{  // 回调 resolve
    // 1. 定义一个线程 , 子进程
    // --------- 执行spwan 方法,使用的子进程
    // --------- 如果执行参数放在args里面,返回一个子进程
    const proc = spawn(...args)
    //2.子、主进程对接
    // --------- 子进程执行的时候,是有一个输出流,也是一个流,
    // --------- 希望把子进程执行的流和主进程的流,进行对接
    // 对接的好处是,子进程的流都可以在主进程看到
    proc.stdout.pipe(process.stdout) // stdout 正常流
    proc.stderr.pipe(process.stderr) //  stderr 异常流
    // 3.回调
    // --------- 当某一个指令完成的时候,会执行close,比如执行npm install,之后就会执行这个close
    // --------- close执行,之后返回执行承诺
    proc.on('close',()=>{
      resolve()
    })
  })

}
module.exports = async name=>{
  //清屏幕
  clear()
  // 打印
  const data= await figlet('mycli-ohh Welocome')
  log(data)
  // 克隆
  log('🚀创建项目:'+name) //  name是传进来的参数,创建的项目名称
  // 当我们去执行 myclie init abc 时,相当于把项目克隆下来,放在abc文件里面
  await clone('github:su37josephxia/vue-template', name)

  // 安装依赖 执行npm install。 子进程的方式去执行,使用spwan来执行
  // 需要对spawn 进行一个小小的改造;比如安装了什么包,输出可以看到
  log('安装依赖')
  // npm是命令  npm后面接的都是参数 install是参数。所以后面的参数是以数组的形式去表示
  await spawn('npm', ['install'],{cwd: `./${name}`}) 
  // {cwd: `./${name}`} 要求在某一个文件夹下面执行
  // 当我们在执行 ohh init abc; 但是我们实际上是想在abc这个文件下去执行npm install

  // 日志
  log(chalk.green(`
安装完成:
To get Start:
================
    cd ${name}
    npm run serve
================
  `))
}

image

启动项目

利用open函数,启动项目

  const open= require('open')
  // 打开浏览器
  module.exports = async name=>{
    ..........
    // 在安装完成之后
    open(`http://localhost:8080`);
    await spawn('npm', ['run', 'serve'], {cwd:`./${name}`})  
  }

约定路由功能

目的:

当我们在views文件夹中添加新的 vue文件时,
image
router.js,也需要新增加载路径
image
App.vue 中也需要添加菜单

image

新加一个视图有两个地方需要变动,router.js需要加一个项目,App.vue也需要加一项。
接下来需要做的就是让他自动产生,类似于umi的命令

步骤:

1.首先用fs读取views里面的文件,根据文件的列表,自动去生成新的router.js里面新的代码。简单的说就是字符串拼接。复杂一些,可以利用模版工具进行渲染。类似组装vue代码。

  1. 利用模板自动渲染;事先设置两个template模板,App.vue 和 router.js;这两个代码模板。模板-router.js里面进行拼装
  2. 把渲染好的代码放在指定位置即可

abc/template/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>

abc/template/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}}
  ]
})

lib/refresh.js

const fs = require('fs')
// Handlebars 是一种简单的 模板语言。
// 它使用模板和输入对象来生成 HTML 或其他文本格式。Handlebars 模板看起来像常规的文本,但是它带有嵌入式的 Handlebars 表达式 。
const handlebars = require('handlebars')

// 粉笔
const chalk = require('chalk')

module.exports = async()=>{
  // 获取列表
  const list =fs.readdirS~~~~ync('./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){
          // 判断模板是否 存在
           // 读成一个字符串
           const content = fs.readFileSync(templatePath).toString()
           //-柯理化方法;两层 
           // -------1.生成编译 输出 生成编译函数(生成代码的代码函数),类似于vue 的compile生成的是render函数;
           // -------2. meta 真正的数据
           const result= handlebars.compile(content)(meta)

           // 写入
           fs.writeFileSync(filePath, result)
           console.log(chalk.green(`🚀 ${filePath} 创建成功`))
        }

}

bin/mycli.js

#!/usr/bin/env node 
// 以sheel 脚本的形式来执行
// 执行解释器类型是node。使用node去执行
// 程序执行入口bin
// 整个程序入口文件mycli

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

// 定制命令行界面的介绍
// 比如我们执行 vue cli 的时候,出来的命令行界面
const program = require('commander')

// -v 时候 的版本号。我们直接用package上的version字段
program.version(require('../package.json').version)

// 创建,命令,命令行定制 
// <name> 代表参数
program
    .command('init <name>')
    .description('init project') //描写
    .action(require('../lib/init')
    )
program
    .command('refresh')
    .description('refresh routers..')
    .action(require('../lib/refresh'))

    console.log('process.argv', process.argv)
  program.parse(process.argv)// 现在进程的argv,最后一行是固定的

image


哦哈哈
26 声望9 粉丝

没比别人多什么天赋;只有努力💪、用心、重复