自动生成vue路由和对应的目录和.vue文件
痛点
每次新增路由的时候,都有几个固定的步骤
- 找到一个文件目录位置
- 创建一个新的.vue文件, 并写入.vue文件的基本代码
- 需要改路由的配置文件,找一个层级位置,添加对应的新路由映射
- 还需把新的.vue文件引入 路由的配置文件(路径要对)
- 路由的层级结构 和 .vue文件的目录层级结构 很难保证一一对应的映射。希望达到绝对的一一映射,这样找文件也更快,更方便,看起来也舒服
每次新增路由都要重复性的走一遍流程,还要去确保层级结构的一一对应,非常麻烦。考虑自动化实现,只需一行命令,就能自动化做完上面的所有事情
实现目标
通过一行命令,比如fe route
- 自动化完成新建一个路由 需要的所有操作。比如:路由树内对应增加一行 包含文件引入。文件树内,对应的目录创建对应的.vue文件
- 路由树和文件树的层级结构保证一一对应
技术实现设计
根据一个配置文件(对象—树),实现目标。例如:
// 配置文件 fe.config.js(根目录下) (注意关注层级结构设计)module.exports = {route: {login: '',index: { // index会特殊处理成 / , 对应 { path: '/' },children: {index: '',template: '',taskList: '',xxx: {children: {a: '',b: '',c: ''}}},redirect: '/login', // 可选配置meta: { title: '234' } // 可选配置},otherPage: {redirect: '/login', // 可选配置meta: { title: '111111111' } // 可选配置}}}
然后输入一个命令 fe route,此处又需要用到我们的老朋友 前端工具平台了:内部前端工具平台搭建
生成的文件如下, 特点是
- 生成的.vue文件,会在src/view目录下,并且层级结构会和配置文件保持一致
- 会生成 autoRoutes.js 路由树,层级结构也会和配置文件保持一致
最终:文件树、路由树、配置文件 的层级结构,都会保持一一对应的完美情况!
- 如果要新增路由,只需在配置文件fe.config.js内,加一行。在执行
fe route,就行了
// 目录结构:├── fe.config.js└── src├── router│ └── autoRoutes.js // 路由文件,详细如下 , 层级结构会和配置文件保持一致└── view├── index│ ├── index.vue│ ├── taskList.vue│ ├── template.vue│ ├── xxx│ │ ├── a.vue│ │ ├── b.vue│ │ └── c.vue│ └── xxx.vue├── index.vue├── login.vue└── otherPage.vue// autoRoutes.js 层级结构会和配置文件保持一致export default [{ path: '/login', name: 'login', component: () => import(/* webpackChunkName: "login" */'@/view/login.vue') },{path: '/', name: 'index', component: () => import(/* webpackChunkName: "index" */'@/view/index.vue'), meta: { title: '234' }, redirect: '/login',children: [{ path: 'index', name: 'indexIndex', component: () => import(/* webpackChunkName: "index-index" */'@/view/index/index.vue') },{ path: 'template', name: 'indexTemplate', component: () => import(/* webpackChunkName: "index-template" */'@/view/index/template.vue') },{ path: 'taskList', name: 'indexTaskList', component: () => import(/* webpackChunkName: "index-taskList" */'@/view/index/taskList.vue') },{path: 'xxx', name: 'indexXxx', component: () => import(/* webpackChunkName: "index-xxx" */'@/view/index/xxx.vue'),children: [{ path: 'a', name: 'indexXxxA', component: () => import(/* webpackChunkName: "index-xxx-a" */'@/view/index/xxx/a.vue') },{ path: 'b', name: 'indexXxxB', component: () => import(/* webpackChunkName: "index-xxx-b" */'@/view/index/xxx/b.vue') },{ path: 'c', name: 'indexXxxC', component: () => import(/* webpackChunkName: "index-xxx-c" */'@/view/index/xxx/c.vue') }]}]},{ path: '/otherPage', name: 'otherPage', component: () => import(/* webpackChunkName: "otherPage" */'@/view/otherPage.vue'), meta: { title: '111111111' }, redirect: '/login' }]// 另外也会给 每个 .vue 文件 初始化一套模板, 如下// 下面的是login.vue,里面的字符串'login',会根据.vue文件的文件名 动态变化的<template><div>login</div></template><script>export default {name: 'login',data () {return {}},watch: {},computed: {},created () {},methods: {}}</script><style lang='' scoped></style>
贴上实现的源代码
- 难点:因为需要读取树,也要生成树形结构。所以要用到递归
- 递归要主要传入的参数,在递归内是有传递性的。还有递归要注意停止条件
// Config 示例, 实际使用时, 会去fe.config.js找// const Config = {// login: '',//// index: { // index会特殊处理成 / , 对应 { path: '/' },// children: {// class: '',// template: '',// taskList: '',// xxx: {// children: {// a: '',// b: '',// c: ''// }// }// },// meta: { title: '234' }// },// otherPage: ''// }/*** 根据fe.config.js内的route对象生成对应的 路由和文件结构* @param{Config: object} fe.config.js内的route对象* @return {无}*/module.exports = (Config) => {const fse = require('fs-extra')const Tpl = require('./tpl.js')const createFile = (path, name) => {const realPath = './src/view/' + path + '.vue'fse.pathExists(realPath).then(exists => {if (!exists) {fse.outputFile(realPath, Tpl.vueTpl(name)).then(_ => {console.log(`${realPath} 生成成功!`)}).catch(err => { console.error(err) })} else {console.log(`${realPath} 已存在, 不做修改!`)}})}const importComponent = (name, parent) => {let arr = [name]if (parent) {arr = [...parent.split('/'), ...arr]}// 里面一定要加"",不然会报错 /* webpackChunkName: "${arr.join('-')}" */return `() => import(/* webpackChunkName: "${arr.join('-')}" */'@/view/${parent ? parent + '/' : ''}${name}.vue')`}// index/template => indexTemplateconst getCamel = (name) => {const arr = name.split('/')if (arr.length === 1) {return name} else {let str = arr.shift()for (const val of arr) {str += val.replace(/^\S/, s => s.toUpperCase())}return str}}const handleRootPath = (key, parent) => {let path = keyif (!parent) { // 根路径if (key === 'index') { // 对根路径的 index 特殊处理成 /path = '/'} else {path = '/' + path}}return path}let row = ''const recursion = (obj, parent) => {let content = ''for (const key in obj) {const val = obj[key]if (!['[object Object]', '[object String]'].includes(Object.prototype.toString.call(val))) {console.error('key对应的value的格式有误, 只能是string或object')return 'key对应的value的格式有误, 只能是string或object'} else {if (val && val.parent) { // 如果配置parent属性, 那可以改变层级安排(为了更容易融进老项目)parent = val.parent}const parentVal = parent ? `${parent}/${key}` : keycreateFile(parentVal, key)row = ` { path: '${handleRootPath(key, parent)}', name: '${getCamel(parentVal)}', component: ${importComponent(key, parent)}`if (typeof val === 'object') { // { meta }if (val.meta) {row += `, meta: ${JSON.stringify(val.meta)}`}if (val.redirect) {row += `, redirect: ${JSON.stringify(val.redirect)}`}if (val.children) {// 如果有父, 保存父的值row += `,children: [${recursion(val.children, parentVal)}]`}}content += row + ' },\n'}}return content}const tpl = `/* 此文件是自动生成的, 请用自助用编辑器格式化 */export default [${recursion(Config)}]`const routeFile = './src/router/autoRoutes.js'fse.outputFile(routeFile, tpl).then(_ => {console.log(routeFile + '生成成功!')}).catch(err => { console.error(err) })}
码字不易,点赞鼓励
