自动生成vue路由和对应的目录和.vue文件

痛点

每次新增路由的时候,都有几个固定的步骤

  1. 找到一个文件目录位置
    1. 创建一个新的.vue文件, 并写入.vue文件的基本代码
  2. 需要改路由的配置文件,找一个层级位置,添加对应的新路由映射
    1. 还需把新的.vue文件引入 路由的配置文件(路径要对)
  3. 路由的层级结构 和 .vue文件的目录层级结构 很难保证一一对应的映射。希望达到绝对的一一映射,这样找文件也更快,更方便,看起来也舒服

每次新增路由都要重复性的走一遍流程,还要去确保层级结构的一一对应,非常麻烦。考虑自动化实现,只需一行命令,就能自动化做完上面的所有事情

实现目标

通过一行命令,比如fe route

  1. 自动化完成新建一个路由 需要的所有操作。比如:路由树内对应增加一行 包含文件引入。文件树内,对应的目录创建对应的.vue文件
  2. 路由树和文件树的层级结构保证一一对应

技术实现设计

根据一个配置文件(对象—树),实现目标。例如:

  1. // 配置文件 fe.config.js(根目录下) (注意关注层级结构设计)
  2. module.exports = {
  3. route: {
  4. login: '',
  5. index: { // index会特殊处理成 / , 对应 { path: '/' },
  6. children: {
  7. index: '',
  8. template: '',
  9. taskList: '',
  10. xxx: {
  11. children: {
  12. a: '',
  13. b: '',
  14. c: ''
  15. }
  16. }
  17. },
  18. redirect: '/login', // 可选配置
  19. meta: { title: '234' } // 可选配置
  20. },
  21. otherPage: {
  22. redirect: '/login', // 可选配置
  23. meta: { title: '111111111' } // 可选配置
  24. }
  25. }
  26. }

然后输入一个命令 fe route,此处又需要用到我们的老朋友 前端工具平台了:内部前端工具平台搭建

生成的文件如下, 特点是

  1. 生成的.vue文件,会在src/view目录下,并且层级结构会和配置文件保持一致
  2. 会生成 autoRoutes.js 路由树,层级结构也会和配置文件保持一致
    最终:文件树、路由树、配置文件 的层级结构,都会保持一一对应的完美情况!
  • 如果要新增路由,只需在配置文件fe.config.js内,加一行。在执行fe route,就行了
  1. // 目录结构:
  2. ├── fe.config.js
  3. └── src
  4. ├── router
  5. └── autoRoutes.js // 路由文件,详细如下 , 层级结构会和配置文件保持一致
  6. └── view
  7. ├── index
  8. ├── index.vue
  9. ├── taskList.vue
  10. ├── template.vue
  11. ├── xxx
  12. ├── a.vue
  13. ├── b.vue
  14. └── c.vue
  15. └── xxx.vue
  16. ├── index.vue
  17. ├── login.vue
  18. └── otherPage.vue
  19. // autoRoutes.js 层级结构会和配置文件保持一致
  20. export default [
  21. { path: '/login', name: 'login', component: () => import(/* webpackChunkName: "login" */'@/view/login.vue') },
  22. {
  23. path: '/', name: 'index', component: () => import(/* webpackChunkName: "index" */'@/view/index.vue'), meta: { title: '234' }, redirect: '/login',
  24. children: [
  25. { path: 'index', name: 'indexIndex', component: () => import(/* webpackChunkName: "index-index" */'@/view/index/index.vue') },
  26. { path: 'template', name: 'indexTemplate', component: () => import(/* webpackChunkName: "index-template" */'@/view/index/template.vue') },
  27. { path: 'taskList', name: 'indexTaskList', component: () => import(/* webpackChunkName: "index-taskList" */'@/view/index/taskList.vue') },
  28. {
  29. path: 'xxx', name: 'indexXxx', component: () => import(/* webpackChunkName: "index-xxx" */'@/view/index/xxx.vue'),
  30. children: [
  31. { path: 'a', name: 'indexXxxA', component: () => import(/* webpackChunkName: "index-xxx-a" */'@/view/index/xxx/a.vue') },
  32. { path: 'b', name: 'indexXxxB', component: () => import(/* webpackChunkName: "index-xxx-b" */'@/view/index/xxx/b.vue') },
  33. { path: 'c', name: 'indexXxxC', component: () => import(/* webpackChunkName: "index-xxx-c" */'@/view/index/xxx/c.vue') }
  34. ]
  35. }
  36. ]
  37. },
  38. { path: '/otherPage', name: 'otherPage', component: () => import(/* webpackChunkName: "otherPage" */'@/view/otherPage.vue'), meta: { title: '111111111' }, redirect: '/login' }
  39. ]
  40. // 另外也会给 每个 .vue 文件 初始化一套模板, 如下
  41. // 下面的是login.vue,里面的字符串'login',会根据.vue文件的文件名 动态变化的
  42. <template>
  43. <div>login</div>
  44. </template>
  45. <script>
  46. export default {
  47. name: 'login',
  48. data () {
  49. return {
  50. }
  51. },
  52. watch: {},
  53. computed: {},
  54. created () {
  55. },
  56. methods: {
  57. }
  58. }
  59. </script>
  60. <style lang='' scoped>
  61. </style>

贴上实现的源代码

  • 难点:因为需要读取树,也要生成树形结构。所以要用到递归
    • 递归要主要传入的参数,在递归内是有传递性的。还有递归要注意停止条件
  1. // Config 示例, 实际使用时, 会去fe.config.js找
  2. // const Config = {
  3. // login: '',
  4. //
  5. // index: { // index会特殊处理成 / , 对应 { path: '/' },
  6. // children: {
  7. // class: '',
  8. // template: '',
  9. // taskList: '',
  10. // xxx: {
  11. // children: {
  12. // a: '',
  13. // b: '',
  14. // c: ''
  15. // }
  16. // }
  17. // },
  18. // meta: { title: '234' }
  19. // },
  20. // otherPage: ''
  21. // }
  22. /**
  23. * 根据fe.config.js内的route对象生成对应的 路由和文件结构
  24. * @param{Config: object} fe.config.js内的route对象
  25. * @return {无}
  26. */
  27. module.exports = (Config) => {
  28. const fse = require('fs-extra')
  29. const Tpl = require('./tpl.js')
  30. const createFile = (path, name) => {
  31. const realPath = './src/view/' + path + '.vue'
  32. fse.pathExists(realPath).then(exists => {
  33. if (!exists) {
  34. fse.outputFile(realPath, Tpl.vueTpl(name))
  35. .then(_ => {
  36. console.log(`${realPath} 生成成功!`)
  37. })
  38. .catch(err => { console.error(err) })
  39. } else {
  40. console.log(`${realPath} 已存在, 不做修改!`)
  41. }
  42. })
  43. }
  44. const importComponent = (name, parent) => {
  45. let arr = [name]
  46. if (parent) {
  47. arr = [...parent.split('/'), ...arr]
  48. }
  49. // 里面一定要加"",不然会报错 /* webpackChunkName: "${arr.join('-')}" */
  50. return `() => import(/* webpackChunkName: "${arr.join('-')}" */'@/view/${parent ? parent + '/' : ''}${name}.vue')`
  51. }
  52. // index/template => indexTemplate
  53. const getCamel = (name) => {
  54. const arr = name.split('/')
  55. if (arr.length === 1) {
  56. return name
  57. } else {
  58. let str = arr.shift()
  59. for (const val of arr) {
  60. str += val.replace(/^\S/, s => s.toUpperCase())
  61. }
  62. return str
  63. }
  64. }
  65. const handleRootPath = (key, parent) => {
  66. let path = key
  67. if (!parent) { // 根路径
  68. if (key === 'index') { // 对根路径的 index 特殊处理成 /
  69. path = '/'
  70. } else {
  71. path = '/' + path
  72. }
  73. }
  74. return path
  75. }
  76. let row = ''
  77. const recursion = (obj, parent) => {
  78. let content = ''
  79. for (const key in obj) {
  80. const val = obj[key]
  81. if (!['[object Object]', '[object String]'].includes(Object.prototype.toString.call(val))) {
  82. console.error('key对应的value的格式有误, 只能是string或object')
  83. return 'key对应的value的格式有误, 只能是string或object'
  84. } else {
  85. if (val && val.parent) { // 如果配置parent属性, 那可以改变层级安排(为了更容易融进老项目)
  86. parent = val.parent
  87. }
  88. const parentVal = parent ? `${parent}/${key}` : key
  89. createFile(parentVal, key)
  90. row = ` { path: '${handleRootPath(key, parent)}', name: '${getCamel(parentVal)}', component: ${importComponent(key, parent)}`
  91. if (typeof val === 'object') { // { meta }
  92. if (val.meta) {
  93. row += `, meta: ${JSON.stringify(val.meta)}`
  94. }
  95. if (val.redirect) {
  96. row += `, redirect: ${JSON.stringify(val.redirect)}`
  97. }
  98. if (val.children) {
  99. // 如果有父, 保存父的值
  100. row += `,
  101. children: [
  102. ${recursion(val.children, parentVal)}
  103. ]`
  104. }
  105. }
  106. content += row + ' },\n'
  107. }
  108. }
  109. return content
  110. }
  111. const tpl = `/* 此文件是自动生成的, 请用自助用编辑器格式化 */
  112. export default [
  113. ${recursion(Config)}]
  114. `
  115. const routeFile = './src/router/autoRoutes.js'
  116. fse.outputFile(routeFile, tpl)
  117. .then(_ => {
  118. console.log(routeFile + '生成成功!')
  119. })
  120. .catch(err => { console.error(err) })
  121. }

码字不易,点赞鼓励