一次前端自动化实践

570 阅读3分钟

最近发现写后台管理页面时有许多重复的工作,浪费很多时间。就想着用node来自动化处理了,同时也可以避免人工操作可能出现的一些失误。本文只针对我自己的项目做出的一些优化,但是提供的一些思路大家可以参考下,灵活运用到自己的项目里去。

每当我想新建一个页面的时候,会有一些列连带的操作:新建一个vue文件>添加路由>在el-menu中添加一个子菜单>添加页面权限相关信息,现在要做的就是把这一系列操作交给node,我们要写一个cli来实现。

认识cli

cli即command-line interface,命令行工具。首先使用npm init创建package.json,然后在其中加入关键代码

"bin":{
    "test": "./index.js"
}

test即为指令名,使用如:test xxx。使用npm link将bin里面的指令链接的全局,方便调试。

接下来来看index.js,它必须是可执行文件,所以我们在头部加上#!/usr/bin/env node

//index.js
#!/usr/bin/env node
console.log('执行了test');

此时在命令行执行test将打印出"执行了test"

commander配合inquirer的命令行交互

program
  .version('1.0.0')
  .command(`add`)
  .action(() => {
    inquirer.prompt([
      {
        type: 'list',
        name: 'page',
        message: '请选择页面等级',
        default: '二级',
        choices: ['一级', '二级']
      },
      {
        type: 'input',
        name: 'parent',
        message: '请输入一级模块的英文名',
        when: answers => answers.page === '二级',
        validate: val=>{
          if (!val) {
            return '输入不能为空'
          }
          return true
        }
      },
      {
        type: 'input',
        name: 'en',
        message: '请输入模块英文名',
        validate: val=>{
          if (!val) {
            return '输入不能为空'
          }
          return true
        }
      },
      {
        type: 'input',
        name: 'cn',
        message: '请输入模块中文名',
        validate: val=>{
          if (!val) {
            return '输入不能为空'
          }
          return true
        }
      },
      {
        type: 'input',
        name: 'code',
        message: '请输入模块编码(权限用)',
        validate: val => {
          if (!val) {
            return '输入不能为空'
          }
          return true
        }
      },
      {
        type: 'input',
        name: 'route',
        message: '请输入页面路径(对应的页面名和路由名)',
        when: answers => answers.page === '二级',
        validate: val => {
          if (!val) {
            return '输入不能为空'
          }
          return true
        }
      }
    ]).then(async answers => {
      // 执行相应的操作
      // 问题的答案都会以每个问题的name字段体现在answers的属性里
      console.log(chalk.green('操作成功'))
    })
  })

收集完我需要用到的一些参数后,就是如何生成对应的内容了。

使用node的fsPromise模块对文件进行读写

const fs = require('fs').promises
const path = require('path')

const fileHandle = await fs.open(path.resolve(yourpath), 'r')
let content = await readHandle.readFile('utf-8')
...
一系列对content的操作
...
await fileHandle.close() // 关闭文件

const writeHandle = await fs.open(path.resolve(yourpath), 'w')
await writeHandle.writeFile(content) // 用新的content覆盖文件
await writeHandle.close()

打开文件并读取文件内容,然后修改文件内容后重新覆盖,这里要注意一点,文档上写的文件标识符"r+"是"打开文件用于读取和写入",但是我还是只能读取,最后只能通过分别打开两次rw来进行读写。

对content的操作

修改文件内容主要用到的是正则,我们通过正则匹配到想要添加内容的位置,一般都会有一个\s可以匹配,然后用String.replace(reg, yourString)替换即可,yourString是上面接收完参数后拼接的字符串。例如我的路由:

const reg = new RegExp('}\\s*]')
const route = `},
   { path: '/${routeName}', component: () => import('@/page/${routePath}') }
]`
routerContent = routerContent.replace(reg, `${route}`)

这里我选择用上面的代码来替换router.js里的}],要替换的地方很多,但都是这个套路。还有正则的前瞻和后顾都是很好的定位手段。

新建页面

就是将一段字符串写入文件,没什么好说的,当文件不存在的时候调用writeFile会自动创建文件。

  const content = `<template>
  <div></div>
</template>

<script>
export default {
  data () {
    return {}
  },
  created () {},
  methods: {}
}
</script>

<style lang="scss" scoped>

</style>
`
  await fs.writeFile(path.resolve(`./src/page/${routePath}.vue`), content)

格式化代码

这样生成的代码都贴在一起格式会很乱,最后我们用eslint自动修复一下。node的child_process模块可以执行shell命令:

child_process.exec(command, [options], callback)

衍生一个 shell 然后在该 shell 中执行 command,需要注意的是,必须将eslint的执行目录添加到这个shell的环境变量中去:

const SEPARATOR = process.platform === 'win32' ? ';' : ':'
const env = Object.assign({}, process.env)

env.Path = path.resolve('./node_modules/.bin') + SEPARATOR + process.env.PATH

const option = {
  env: env
}
child_process.exec('eslint --fix --ext .js,.vue src', option, callback)

最后将完成的代码上传的npm。至此,大功告成!运行test add查看效果就行了!

总结

我们将所有手动修改的地方都挪到命令行执行啦,简直太方便啦,觉得有帮助的话就点个赞吧( •̀ ω •́ )✧