node编写cli命令行

Posted LiuJun2Son

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了node编写cli命令行相关的知识,希望对你有一定的参考价值。

该文章是在 node编写cli命令行(二) 的基础上继续编写的

1.编写 addcom 指令

编写一个 addcom 指令,例如当执行:vue-temp-cli addcom HelloWorld -d /src/view/main 时,会在/src/view/main目录下新建一个 HelloWorld 组件

1.添加模板文件

新建一个 template 文件夹

lib
	|--
index.js
template 
    |-- mockjs
    `-- src
        |-- base-ui
        |   `-- src
        |       `-- components
        |-- components
        |   `-- hello-world.vue.ejs
        |-- service
        |-- store
        `-- view
            |-- login
            |   |-- login.vue.ejs
            |   `-- route.js.ejs
            `-- main

hello-world.vue.ejs 文件

<%_ if (data)  _%>
<template>
  <div class="<%= data.name %>">
    <h1> msg </h1>
  </div>
</template>

<script>
export default 
  name: '<%= data.humpName %>',
  components: 

  ,
  mixins: [],
  props: 
    msg: 
      type: String,
      default: '<%= data.humpName %>'
    
  ,
  data: function() 
    return 

    
  ,
  computed: 

  ,
  watch: 

  ,
  created() 

  ,
  mounted() 

  ,
  methods: 

  


</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.<%- data.name %>


</style>
<%_  _%>

2.编写 addcom 指令

1.修改index.js文件

添加新建项目的 addcom 指令, 看下面的第5步

#!/usr/bin/env node
var create = require('./lib/create')
// 导入addcom.js文件
var addcom = require('./lib/addcom')
var program = require('commander');

// 1.添加版本
program.version(require('./package.json').version,  '-v, --version')

// 2.添加 options 选项(可供后面定义的指令使用该选项,获取选项的属性 program.xxx )
program
  .option('-d, --dir <dir>', '指定目录路劲,例如,src/view/main/。错误:/src/view/main/', './') // 获取 program.dir

// 3.添加create指令
.....

// 5.添加 addcom 指令,例如:vue-temp-cli addcom Xxx -d src/view/main/
program
  .command('addcom <name>')
  .description('add component, 例如:vue-temp-cli addcom XXX -d src/view/main/')
  .action((name)=>
    addcom.addcompoent(name, program.dir)
  )

// 4.添加help提示信息
.....
program.parse(process.argv);

2.编写 addcom.js 文件

该文件是addcom 指令 代码的具体实现过程。

1)获取模板需要的数据( 并且定义好:模板文件路径,生成目标文件的路劲 )

2)开始编译模板,使用(模板文件,数据),返回编译后的结果

3)把编译后的结果写到指定 生成目标文件的路劲

const utils = require('./utils')

/**
 * 1.新建一个组件
 * @param  name 组件的名称 hello-world  helloworld helloWorld HelloWorld  Helloworld
 * @param * dir 新建组件存放的目录(该路径不能以/开头)
 */
const addcompoent = async (name, dir)=>
  // 1.获取模板需要的数据
  const data = utils.getTemplateData(name, dir)
  // 模板文件路径
  const templateFilePath = utils.resolveReallyPath('../template/src/components/hello-world.vue.ejs')
  // 生成目标文件的路劲
  const targetFilePath = utils.resolveRelativePath(`$dir/$data.name.vue`)
  // 2.开始编译模板(模板文件,数据)
  utils.compiler(templateFilePath, data)
  // 3.编译成功,新建文件
  .then((str)=>
    // 新建目录
    utils.mkdirsSync(dir)
    // 新建文件
    utils.generateFile(targetFilePath, str)
  )
  // console.log(name, dir)
  // console.log('utils=', utils.resolveReallyPath('../'))
  // console.log('utils=', utils.resolveRelativePath(dir))



module.exports = 
  addcompoent

3.安装模板引擎ejs

上面的模板编译使用到了ejs的模板引擎

npm install ejs

PS F:\\blog\\node-cli\\vue-temp-cli> npm install ejs  

> ejs@3.1.3 postinstall F:\\blog\\node-cli\\vue-temp-cli\\node_modules\\ejs
> node --harmony ./postinstall.js

Thank you for installing EJS: built with the Jake javascript build tool (https://jakejs.com/)

npm WARN vue-temp-cli@0.0.1 No description
npm WARN vue-temp-cli@0.0.1 No repository field.

+ ejs@3.1.3
added 10 packages from 5 contributors in 2.364s
PS F:\\blog\\node-cli\\vue-temp-cli>

4.编写 utils.js 的工具类

该工具类是用来获取 模板所需要的数据

.....
.....

const path = require('path')
const fs = require('fs')
// 模板引擎
const ejs = require('ejs')
const nameUtils = require('./name-utils')

const log = (str)=>
  console.log(chalk.green(str))

const errorLog = (str)=>
  console.log(chalk.red(str))


// 1.打印欢迎界面
......

// 2.克隆模板项目(Download a git `repository` to a `destination` folder  with `options`  )
......

// 3.获取可以 执行终端命令的 子线程
.......

// 4.获取绝对路径(就是这个文件所在的路径),__dirname 总是指向被执行 js 文件的绝对路径
const resolveReallyPath = (...file) => path.resolve(__dirname, ...file)

// 5.获取相对路径( ./ 是获取命令行执行命令时所在的路径)
const resolveRelativePath = (...file) => path.resolve('./', ...file)

/**
 * 6.模板的编译
 * @param * templateFilePath 模板文件的路劲
 * @param * data ejs模板数据,  
 * @param * options ejs模板选项,  
 */
const compiler = (templateFilePath, data=, options=)=>
  return new Promise((resolve,reject)=>
    ejs.renderFile(templateFilePath, data, options, (err, str) => 
      if (!err) 
        // 编译成功
        resolve(str)
       else 
        // 编译失败
        errorLog(err.message)
        reject(err)
      
    )
  )


/**
 * 7.生成文件
 * @param * path 生成文件存放的路径
 * @param * data 文件的字符串内容
 */
const generateFile = (path, data) => 
  if (fs.existsSync(path)) 
    errorLog(`$args.dir组件已存在`)
    // 退出程序
    process.exit(0)
  
  return new Promise((resolve, reject) => 
    // 写文件到指定的文件下
    fs.writeFile(path, data, 'utf8', err => 
      if (err) 
        errorLog(err.message)
        reject(err)
       else 
        resolve(true)
      
    )
  )


/**
 * 8.获取ejs模板对应的数据
 * @param * name 
 */
const getTemplateData = (name,dirPath)=>
  // src/view/login/login  1;  src/view/main/broad/broad  2;
  let pathDir = dirPath+'/'+name
  pathDir = pathDir.split('/').filter((v)=>return v!=='') // [src,view,login,login]
  return 
    // dirPath :getDirPath(name), // 新建组件的路劲
    dirPath, // 新建组件的路劲 src/view/main/
    name :nameUtils.getComponentDirName(name), // 组件的名称(小写) demo1btn 或者  demo1btn 或者 demo1-btn
    humpName : nameUtils.getComponentName(name), // 组件的名称(首字母大写并驼峰命名) Demo1Btn
    firLowName : nameUtils.getComponentNameFirLow(name), // 组件的名称(首字母小写,其它字符首字符大写)demo1Btn
    routeLevel : nameUtils.getRouteLevel(pathDir.join('/')) -3 , // 组件路由的级别(1,2,3)
    parentRouteName : nameUtils.getParentRouteName(name), // 组件父亲路由的名称 main 、 ''  、
  


/**
 * 9.递归新建目录(已存在不用管,不存在就新建)
 * @param * dirname  /src/view/main/
 */
function mkdirsSync(dirname) 
  // 存在,跳过
  if (fs.existsSync(dirname)) 
    return true
   else 
    // 不存在,判断父亲文件夹是否存在?
    if (mkdirsSync(path.dirname(dirname))) 
      // 存在父亲文件,就直接新建该文件
      fs.mkdirSync(dirname)
      return true
    
  


module.exports = 
  .....
  .....  
  resolveReallyPath,
  resolveRelativePath,
  compiler,
  getTemplateData,
  generateFile,
  mkdirsSync

5.编写 name-utils.js 工具类


/**
 * 首字母 大写
 * @param * name  Name
 */
const upperCaseFirstName = (name) => 
  return name.charAt(0).toUpperCase() + name.slice(1)


/**
 * 首字母 小写
 * @param * name name
 */
const lowerCaseFirstName = (name) => 
  return name.charAt(0).toLowerCase() + name.slice(1)


/**
 * 获取组件文件夹的名称
 * @param * dirName  dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
 * @param return demo1btn 或者  demo/demo1btn 或者 demo/demo1-btn
 */
const getDirPath = (dirName) => 
  // 2.转成小写
  return dirName.toLowerCase()


/**
 * 获取组件文件夹的名称
 * @param * dirName  dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
 * @param return demo1btn 或者  demo1btn 或者 demo1-btn
 */
const getComponentDirName = (dirName) => 
  // 1.先分词
  const dirs = dirName.split('/')
  const componentDirName = dirs.pop()
  // 2.转成小写
  return componentDirName.toLowerCase()


/**
 * 获取组件的名称
 * @param * dirName  dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
 * @param return // Demo1btn 或者  Demo1btn 或者 Demo1Btn
 */
const getComponentName = (componentName) => 
  // 1.先分词
  const names = componentName.split('/')
  let compName = names.pop()
  compName = compName.toLowerCase()
  const compNames = compName.split('-')
  for (let i = 0; i < compNames.length; i++) 
    compNames[i] = upperCaseFirstName(compNames[i])
  
  const name = compNames.join('')
  return name


/**
 * 获取组件的名称
 * @param * dirName  dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
 * @param return // demo1btn 或者  demo1btn 或者 demo1Btn
 */
const getComponentNameFirLow = (componentName) => 
  // 1.先分词
  const names = componentName.split('/')
  let compName = names.pop()
  compName = compName.toLowerCase()
  const compNames = compName.split('-')
  for (let i = 0; i < compNames.length; i++) 
    compNames[i] = upperCaseFirstName(compNames[i])
  
  const name = compNames.join('')
  return lowerCaseFirstName(name)


/**
 * 获取是一级路由,还是二级路由,还是三级路由
 * @param * dirName  dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
 * @param return // 1 或者  1 或者  2
 */
const getRouteLevel = (dirName) => 
  // 1.先分词
  const names = dirName.split('/')
  return names.length


/**
 * 获取父亲路由名称
 * @param * dirName  dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
 * @param return // '' 或者  demo 或者  demo
 */
const getParentRouteName = (dirName) => 
  // 1.先分词
  const names = dirName.split('/')
  let result = ''
  if (names.length > 1) 
    result = names[names.length - 2] // 倒数第二个
  
  return result


// console.log(getParentRouteName('demo1btn'))
// console.log(getParentRouteName('demo/demo1btn'))
// console.log(getParentRouteName('demo/demo1-btn'))
// console.log(getParentRouteName('demo/Demo1-Btn'))
// console.log(getParentRouteName('demo/Demo1-btn/sd'))

module.exports = 
  getDirPath,
  getComponentName,
  getComponentDirName,
  getComponentNameFirLow,
  getRouteLevel,
  getParentRouteName,

  upperCaseFirstName,
  lowerCaseFirstName


3.测试addcom指令

执行 vue-temp-cli addcom hellow-World -d src/components 命令,就会在执行该命令的路劲下:新建src/components/这个目录,然后在该目录下新建hellow-World.vue文件。


# 在执行指令目录下下面一个`hellow-World.vue`文件
vue-temp-cli addcom hellow-World 

# 在执行指令目录下新建src/components/文件夹,然后在该文件夹下面新建一个`hellow-World.vue`文件
vue-temp-cli addcom hellow-World -d src/components/
vue-temp-cli addcom hellow-World -d src/components

# 下面这种是错误的写法(不能以/开头)
vue-temp-cli addcom hellow-World -d  /src/components

2.编写addPage 指令

1.添加模板

login.vue.ejs

<%_ if (data)  _%>
<template>
  <div class="<%= data.name %>">
    <h1> msg  Page</h1>
  </div>
</template>

<script>
export default 
  name: '<%= data.humpName %>',
  components: 

  ,
  mixins: [],
  props: 
    msg: 
      type: String,
      default: '<%= data.humpName %>'
    
  ,
  data: function() 
    return 

    
  ,
  computed: 

  ,
  watch: 

  ,
  created() 

  ,
  mounted() 

  ,
  methods: 

  


</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.<%- data.name %>


</style>
<%_  _%>

route.js.ejs


<%_ if (data)  _%>
// 普通加载路由
// import <%= data.humpName %> from './<%= data.name %>.vue'
// 懒加载路由
const <%= data.humpName %> = () => import(/* webpackChunkName: "<%= data.name %>" */ './<%= data.name %>.vue')
export default 
  <%_ if (data.routeLevel === 1)  _%>
  path: '/<%= data.name %>', // 一级路由前面多一个 /
  <%_  else  _%>
  path: '<%= data.name %>', // 二级路由前面没有/
  <%_  _%>
  name: '<%= data.name %>',
  pname: '<%= data.parentRouteName %>', // 父亲路由的名称
  level: <%= data.routeLevel %>, // <%= data.routeLevel %>级路由
  component: <%= data.humpName %>,
  children: [
  ]

<%_  _%>

2.编写addPage指令

1.修改 index.js 文件

在第6步中添加 addPage 指令

#!/usr/bin/env node
var create = require('./lib/create')
var addcom = require('./lib/addcom')
var addPage = require('./lib/addPage')
var program = require('commander')

// 1.添加版本
program.version(require('./package.json').version,  '-v, --version')

// 2.添加 options 选项(可供后面定义的指令使用该选项,获取选项的属性 program.xxx )
.....

// 3.添加create指令
.....

// 5.添加 addcom 指令,例如:vue-temp-cli addcom Xxx -d src/view/main/
.....

// 6.添加 addPage 指令,例如:vue-temp-cli addPage Xxx -d src/view/main/
program
.command('addPage <name>')
.description('add page component, 例如:vue-temp-cli addPage XXX -d src/view/main/')
.action((name)=>
  addPage.addPageCompoent(name, program.dir)
)

// 4.添加help提示信息
.....
program.parse(process.argv);

2.编写 addPage.js 文件

在 addPage.js 文件是 addPage 指令代码具体实现

1)自动生成.vue文件

​ 1.获取模板需要的数据( 并且定义好:模板文件路径,生成目标文件的路劲 )

​ 2.开始编译模板,使用(模板文件,数据),返回编译后的结果

​ 3.把编译后的结果写到指定 生成目标文件的路劲

2)自动生成route.js文件

​ 1.获取模板需要的数据( 并且定义好:模板文件路径,生成目标文件的路劲 )

​ 2.开始编译模板,使用(模板文件,数据),返回编译后的结果

​ 3.把编译后的结果写到指定 生成目标文件的路劲

const utils = require('./utils')

const addPageCompoent = (name, dir)=>
  // 1.自动生成.vue文件
  addPageVue(name, dir)
  // 2.自动生成route.js文件
  addPageRoute(name, dir)


const addPageVue = (name, dir)=>
    // 1.获取模板需要的数据
    const data = utils.getTemplateData(name, dir)
    const templateFilePath = utils.resolveReallyPath('../template/src/view/login/login.vue.ejs')
    const targetFilePath = utils.resolveRelativePath(`$dir/$data.name.vue`)
    // 2.开始编译模板(模板文件,数据)
    utils.compiler(templateFilePath, data)
    // 3.编译成功后新建文件
    .then((str)=>
      utils.mkdirsSync(dir)
      utils.generateFile(targetFilePath, str)
    )


const addPageRoute = (name, dir)=>
    // 1.获取模板需要的数据
    const data = utils.getTemplateData(name, dir)
    const templateFilePath = utils.resolveReallyPath('../template/src/view/login/route.js.ejs')
    const targetFilePath = utils.resolveRelativePath(`$dir/route.js`)
    // 2.开始编译模板(模板文件,数据)
    utils.compiler(templateFilePath, data)
    // 3.编译成功后新建文件
    .then((str)=>
      utils.mkdirsSync(dir)
      utils.generateFile(targetFilePath, str)
    )

module.exports = 
  addPageCompoent

3.测试addPage指令

执行 vue-temp-cli addPage login -d src/view/login/ 命令,就会在执行该命令的路劲下:新建src/view/login/这个目录,然后在该目录下新建login.vue, 和 route.js文件。

# 在执行指令目录下下面一个以上是关于node编写cli命令行的主要内容,如果未能解决你的问题,请参考以下文章

node编写cli命令行

node编写cli命令行

node编写cli命令行

node编写cli命令行

手把手教你使用nodejs编写cli(命令行)

Node.js 命令行工具的编写