Yargs写一个新建模板工具

3,052 阅读3分钟

Yargs官方地址

以往每次建模块都需要手动创建觉得很繁琐,看了公司项目的有自动化创建的工具,参考了下,是用到Yargs,能够自定义命令、参数来处理问题。通过学习yargs,顺便自己写一个自动化创建模块工具出来。

1. 步骤

  • 第一步:需要传递的参数

     存在三个参数
     file: 文件夹以及模块名(必填)
     path: 文件存放路径
     type: 模板类型
  • 第二步:创建一个命令,并且处理传入的参数

     command(cmd, desc, [builder], [handler])
     用command方法创建了一个命令,将先前定义的参数放到command的bulider里面。
     [handler]处理传参。
  • 第三步:处理参数

     1.创建文件夹
     2.创建文件
  • 第四步:自定义模板

     能够创建文件是不够的,我们需要文件内容是我们定义好的模板内容,所以我们先定义好模板。
  • 第五步:做一些体验优化,创建文件提示,重复文件提示

2. 目录

ReactTemplat、VueTemplate: 是自定义模板,可以根据自己的模板内容、结构来自定义。

cli.js: 用来创建命令,并且将参数传给start文件处理。

start.js: 用来处理参数,创建文件夹及文件,利用process进程与用户进行交互。

handle.js: 处理文件的方法。

3. 帮助信息

node ./tools/cli add -h   // 显示帮助信息

Options:
  --version   Show version number                                      [boolean]
  -h          Show help                                                [boolean]
  --file, -f  create a file                                          [required]
  --path, -p  file path                                            [default: ""]
  --type, -t  file‘s type             [choices: "vue", "react"] [default: "vue"]

Examples:
  node tools/cli add  -p views -f test -t vue  在views目录下创建一个test模板 
  

4.用法

node tools/cli add  -f test -p views -t vue
在views目录下创建一个test模块,模块类型是vue。

新建模块时会先判断存放路径是否存在该文件夹。

遇到重复的模块可选是否需要覆盖

文件地址

cli.js

const argv = require('yargs')
  .command(
    'add',
    'create a file',
    function(yargs) {
      return yargs
        .option('file', {
          alias: 'f',
          describe: 'create a file'
        })
        .option('path', {
          alias: 'p',
          describe: 'file path',
          default: ''
        })
        .option('type', {
          alias: 't',
          describe: 'file‘s type',
          choices: ['vue', 'react'], // 现在有两个模板供选择,可以根据自己的项目情况设计模板
          default: 'vue'
        })
        .demandOption(['file'], 'Please provide file to work with this tool')
        .example(
          'node tools/cli add  -p views -f test -t vue',
          '在views目录下创建一个test模板'
        )
    },
    function(argv) {
      // 根据参数,创建模板
      start(argv)
    }
  )
  .help('h').argv

start.js

const handel = require('./handle')
const colors = require('colors')
const path = require('path')
module.exports = async function(argv) {
  argv.file = argv.file.toString()
  const existPathFolder = await handel.existFolder(path.resolve(argv.path))
  const fileName =
    argv.file.substring(0, 1).toUpperCase() + argv.file.substring(1)
  let className = ''

  for (let i = 0; i < argv.file.length; i++) {
    if (/[A-Z]/.test(argv.file[i])) {
      className += '-'
    }
    className += argv.file[i].toLowerCase()
  }
  if (argv.path !== '') {
    argv.path += '/'
  }
  const filePath = path.resolve(argv.path) + '/' + fileName
  process.stdin.setEncoding('utf8')

  const createFileData = {
    filePath,
    fileName,
    className,
    type: argv.type
  }

  // 不存在path的文件夹
  if (!existPathFolder) {
    console.warn(
      colors.green(`是否创建 ${path.resolve(argv.path)} 文件夹?:y/n`)
    )

    process.stdin.on('data', async chunk => {
      chunk = chunk.replace(/[\s\n]/, '')
      if (chunk === 'y') {
        // 创建path文件夹
        await handel.createFolder(path.resolve(argv.path))

        // 创建组件文件夹
        await handel.createFolder(filePath)

        // 创建文件
        await handel.createFile(createFileData)
        process.exit()
      } else if (chunk === 'n') {
        process.exit()
      } else {
        console.warn(colors.red('请输入正确指令:y/n'))
        process.exit()
      }
    })
  } else {
    // 判断组件文件夹是否存在
    const existFileFolder = await handel.existFolder(filePath)
    if (existFileFolder) {
      console.warn(colors.green(`${fileName}文件夹已存在,是否覆盖?:y/n`))
      process.stdin.on('data', async chunk => {
        chunk = chunk.replace(/[\s\n]/, '')
        if (chunk === 'y') {
          // 创建组件文件夹
          await handel.createFolder(filePath)

          // 创建文件
          await handel.createFile(createFileData)

          process.exit()
        } else if (chunk === 'n') {
          process.exit()
        } else {
          console.warn(colors.red('请输入正确指令:y/n'))
          process.exit()
        }
      })
    } else {
      // 创建组件文件夹
      await handel.createFolder(filePath)

      // 创建文件
      await handel.createFile(createFileData)

      process.exit()
    }
  }
}

handle.js

const fs = require('fs')
const colors = require('colors')
const path = require('path')
module.exports = {
  existFolder: async function(path) {
    // 判断是否存在argv.path的文件夹
    return new Promise(function(resolve, reject) {
      return fs.exists(path, e => {
        resolve(e)
      })
    })
  },
  /**
   *创建文件夹
   @param filePath 文件路径
   */
  createFolder: function(filePath) {
    return new Promise(function(resolve, reject) {
      fs.mkdir(filePath, function(err) {
        if (err) {
          if (err.errno === -2) {
            console.log(colors.red('找不到目录'))
          } else if (err.errno === -17) {
          }
        } else {
          console.log(colors.green('创建文件夹: '))
          console.log(colors.underline(`${filePath}`))
        }
        resolve()
      })
    })
  },
  /**
   * @param args:{
   *  filePath 文件路径
   *  fileName 文件名
   *  className 样式名
   *  type 文件类型
   * }
   */
  createFile: function({ filePath, fileName, className, type }) {
    const data = {
      fileName,
      filePath,
      className
    }
    // 模板路径
    switch (type) {
      case 'vue':
        data.templateFolderPath = path.join(__dirname, './VueTemplate')
        break
      case 'react':
        data.templateFolderPath = path.join(__dirname, './VueTemplate')
        break
      default:
        data.templateFolderPath = path.join(__dirname, './VueTemplate')
    }
    return new Promise(async (resolve, reject) => {
      await this.readAndWiteFile(data, resolve)
    })
  },
  /**
   * 读取模板内容并且写到新建文件里面
   * @param args:{
   *  templateFolderPath 模板路径
   *  fileName 文件名
   *  filePath 文件路径
   *  className 样式名字
   * }
   * @param resolve
   */
  readAndWiteFile: function(
    { templateFolderPath, fileName, filePath, className },
    resolve
  ) {
    fs.readdir(templateFolderPath, 'utf8', (err, files) => {
      if (err) {
        console.log(colors.red(err))
        return false
      }
      files.forEach(templateName => {
        const FileName = templateName
          .replace('TemplateName', fileName)
          .replace('.txt', '')

        // 1.创建文件
        fs.createWriteStream(`${filePath}/${FileName}`)
        // 2.读取、写入模板内容
        const content = fs
          .readFileSync(`${templateFolderPath}/${templateName}`)
          .toString() // 读取模板文件
          .replace(/\${TemplateName}/g, FileName.split('.')[0])
          .replace(/\${template-name}/g, className) // 替换模板内容
        // 将templateName替换成对应的文件名
        fs.writeFileSync(`${filePath}/${FileName}`, content, 'utf8')

        console.log(colors.green('写入文件: '))
        console.log(colors.underline(`${filePath}/${FileName}`))
      })

      resolve()
    })
  }
}

这种方法比较繁琐,下面优化了两个版本

1.无需填写手写路径跟类型

(1)命令: node tools2/cli add -f fileName

(2)提示选择模板

(3)选择放置位置,提供进入下一层,返回上一层操作

2. 无需逐层选择路径

针对上面一旦出现深层路径的话,操作会很繁琐,所以提供动态选择路径,根据关键字提示路径地址。

步骤

(1)命令: node tools3/cli

(2)填写文件名

(3)提示选择模板

(4)提示放置地址