webpack自动路由插件

由于之前的搭建的单页面架构自动路由这块,配置meta信息,需要单独写配置文件,而且只配置支持二级路由,三级路由需要手写配置生成。动态路由也是需要配置id。对使用者不太友好。现在就想使用webpack 插件的形式,自动生成路由文件,meta信息在每个vue页面里面去配置,支持多级路由,来优化路由这块的代码。(网上也有一些自动路由插件,但不太合适接入到之前写的单页面架构中,于是就自己动手写了一个)

整体思路

1、写一个webpack插件,在代码运行时生成routes.js文件
2、代码运行时传入需要生成文件的目录,以及输出routes.js的目录路径
3、根据目录路径(system/user/index.vue)生成 嵌套树

system:{
    value:[],
    children:[]
}

4、根据嵌套树在递归遍历配置路由信息生成json数据
5、nodejs读取目录下vue文件里面的meta信息整合到路由Json中

[ { name: 'system',
    path: 'system',
    component: '@/views/autoRouter/system/_layout.vue',
    index: 1,
    meta:
     '{\n    title: \'系统管理\',\n    icon: \'form\',\n    permissionArray: [1, 2, 3],\n    sortIndex: 1,\n    newTime: \'2022-05-20\'\n  }',
    children: [ [Object], [Object], [Object] ] } ]

6、在将路由json数据递归匹配可生成routes.js 的字符串数据
7、使用nodejs 在输出的目录下写入routes.js 文件

代码实现

const fg = require('fast-glob') // 读取文件目录
const prettier = require('prettier') // 处理文件格式

1、定义一个自动路由插件类,在这个类里面定义一个方法实现目录路径输入,routes.js文件输出数据。在写入文件中

// 定义一个自动路由插件类
    const generate = () => {
      const code = generateRoutes(this.options)
      let to
      // 处理路由文件生成目录 默认与插件文件并级 如果配置routePath,那生成文件就是此路径下
      if (this.options.routePath) {
        to = path.join(process.cwd(), this.options.routePath)
      } else {
        to = path.join(__dirname, './routes.js')
      }
      // 以同步的方法检测目录是否存在。 如果目录存在 返回true,如果目录不存在 返回false 语法
      // 读取文件信息 如果没有改变直接返回
      if (fs.existsSync(to) &&
        fs.readFileSync(to, 'utf8').trim() === code.trim()) {
        return
      }
      fs.writeFileSync(to, code)
    }

2、将目录生成嵌套树

/**
 * 根据目录去生成路由
 * @param pages 解析路由文件目录
 * @param importPrefix import导入前缀目录
 * @param layout 想要生成嵌套路由,目录下需要layout文件,用于配置父节点的meta信息 另个作用是当路由大于二级路由时,_layout需要有<router-view/>,不然页面不会显示
 * @param common 过滤文件路径路径
 * @returns {*}
 */
function generateRoutes({ pages = 'src/views', importPrefix = '@/views/', layout = '_layout.vue', common = 'common' }) {
  // 指定文件不需要生成路由配置
  const patterns = ['**/*.vue', `!**/${layout}`, `!**/${common}/*.vue`]
  // 获取所有layout的文件路径
  const layoutPaths = fg.sync(`**/${layout}`, {
    cwd: pages,
    onlyFiles: true
  })
  // 获取所有需要路由配置的文件路径
  const paths = fg.sync(patterns, {
    cwd: pages,
    onlyFiles: true
  })
  const pathsArr = paths.map((p) => p.split('/'))
  const layoutPathsArr = layoutPaths.map((p) => p.split('/'))
  // 生成嵌套目录
  const map = {}
  layoutPathsArr.forEach((path) => {
    const dir = path.slice(0, path.length - 1)
    dir.unshift('rootPathLayoutName')
    setToMap(map, pathToMapPath(path.slice(0, path.length - 1)), path)
  })
  pathsArr.forEach((path) => {
    let dir = path
    if (path.indexOf('index.vue') > -1) {
      dir = path.slice(0, path.length - 1)
    }
    setToMap(map, pathToMapPath(dir), path)
  })

  //  将目录匹配生成路由json
  const routerStr = pathMapToMeta({
    children: map.children,
    routers: [],
    pages: pages,
    importPrefix: importPrefix
  })
  // 将json转换文件字符串
  return createRoutes(routerStr)
}
/**
 * 将目录生成嵌套树 将每个目录生成key:{value:[],childer:[]}
 * @param map {}
 * @param paths 不带.vue的路径
 * @param value 所有解析处理的层级目录数组
 */
function setToMap(map, paths, value) {
  const target = paths.reduce((item, key) => {
    if (!item.children) {
      item.children = new Map()
    }
    let child = item.children.get(key)
    if (!child) {
      child = {}
      item.children.set(key, child)
    }
    return child
  }, map)
  target.value = value
}

3、递归处理将嵌套树生成路由json数据,并读取vue页面中的meta信息
注意:想要生成嵌套路由,目录下需要layout文件,用于配置父节点的meta信息 另个作用是当路由大于二级路由时,_layout需要有,不然页面不会显示

/**
 *
 * @param children 嵌套目录
 * @param routers 路由
 * @param pages 需要自动生成文件的目录
 * @param importPrefix import 引入页面文件的前缀目录
 * @returns {*[]}
 */
function pathMapToMeta({ children, routers = [], pages, importPrefix }) {
  Array.from(children.keys()).forEach((row) => {
    const item = children.get(row)
    // 配置参数
    const router = {
      name: row,
      path: (row.indexOf('_id') > -1 ? (row.indexOf('_id') === 0 ? row.replace('_', ':') : row.replace('_', '/:')) : row),
      component: '',
      index: 1,
      meta: `{ title: "${row}", icon: "form"}`,
      children: []
    }

    // 如果value有值,说明可以根据文件路径取meta信息
    if (item.value) {
      router.component = path.join(importPrefix, item.value.join('/'))
      const file = fs.readFileSync(path.join(pages, item.value.join('/')), 'utf8')
      const metaArr = file.match(/\meta: {[^\{]+\}/g) || file.match(/\meta:{[^\{]+\}/g)
      if (metaArr) {
        const metaStr = metaArr[0]
        // 匹配meta信息
        let meta = ''
        if (metaStr.indexOf('meta') > -1) {
          meta = metaStr.substring(metaStr.indexOf('{'), metaStr.indexOf('}') + 1)
        }
        router.meta = meta
        // 匹配排序
        if (metaStr.indexOf('sortIndex') > -1) {
          const sortIndexAll = metaStr.substring(metaStr.indexOf('sortIndex'), metaStr.indexOf('}'))
          const sortIndex = sortIndexAll.substring(sortIndexAll.indexOf(':') + 1, (sortIndexAll.indexOf(',') > -1 ? sortIndexAll.indexOf(',') : sortIndexAll.length - 1))
          router.index = Number(sortIndex)
        }
      }
    }
    // 如果有children 需要遍历循环匹配
    if (item.children) {
      router.children = pathMapToMeta({
        children: item.children,
        routers: router.children,
        pages: pages,
        importPrefix: importPrefix
      })
    }
    routers.push(router)
  })
  return routers
}

4、将路由json数据匹配生成routes.js 文件字符串数据


/**
 * 将路由json格式化成字符串
 * @param routers 路由json
 * @returns {*}
 */
function createRoutes(routers) {
  const code = routers.map(createRoute)
  return prettier.format(`import Layout from '@/layout'\nexport default [\n
    ${code}
  \n]`, {
    parser: 'babel',
    semi: false,
    singleQuote: true,
    trailingComma: 'none' // 处理最后一行不加,的问题
  })
}

/**
 * 一级路由转换 第一级的时候需要把component换成Layout 如果没有子节点,则需要将父节点复制一份成为子节点,component指向文件路径
 * @param map
 * @param children
 * @returns {string}
 */
function createRoute(map, children = {}) {
  if (map.children && map.children.length !== 0) {
    children = map.children.map(createRouteZJ)
    return `\n{
      path:'/${map.path}',
      name:'${map.name}',
      meta:${map.meta},
      index:${map.index},
      alwaysShow: false,
      component: Layout,
      children:[${children}]
    }`
  } else {
    // 如果只有一级目录,需要单独处理name 不然会报警告name相同
    children = `\n{
      path:'${map.path}',
      name:'${map.name}',
      meta:${map.meta},
      index:${map.index},
      alwaysShow: false,
      component:() => import('${map.component}')
    }`
    return `\n{
      path:'/${map.path}',
      name:'${map.name}p',
      meta:${map.meta},
      index:${map.index},
      alwaysShow: false,
      component: Layout,
      children:[${children}]
    }`
  }
}

/**
 * 二级及以上路由转换
 * @param map json里面的children  子节点里面的path不需要'/'
 * @param children
 * @returns {string}
 */
function createRouteZJ(map, children = {}) {
  if (map.children) {
    children = map.children.map(createRouteZJ)
  }
  return `\n{
  path:'${map.path}',
  name:'${map.name}',
  meta:${map.meta},
  index:${map.index},
  component:() => import('${map.component}'),
  children:[${children}]
  }`
}

使用方式

1、导入

npm install  ff-auto-router

2、修改vue.config.js

const autoRouter = require('ff-auto-router/lib/router-webpack-plugin')
configureWebpack(config) {
    config.plugins = [
      ...config.plugins,
      // eslint-disable-next-line new-cap
      new autoRouter({
        pages: 'src/views/autoRouter',
        importPrefix: '@/views/autoRouter',
        routePath: 'src/router/routes.js'
      })
    ]
}

pages 需要自动生成文件的目录
importPrefix import 引入页面文件的前缀目录
routePath 路由生成的文件目录,如果设置了则会在当前项目指定目录生成路由文件,否则可以从ff-auto-route导入 ff-auto-router/lib/routes

其中metavue-router配置的meta属性一致
写在每个组件的export default {}中

meta: {
    title: '系统管理',
    icon: 'form',
    permissionArray: [1, 2, 3],
    sortIndex: 1,
    newTime: '2022-05-20'
  }

3、修改src/router/index.js 文件,
这里的配置根据项目实际情况写。这样是基本写法可使用

import routes from './routes'
const createRouter = () => new Router({
  mode: 'history', // require service support
  base: process.env.BASE_URL,
  scrollBehavior: () => ({ y: 0 }),
  routes
})

展示效果

目录文件

{
    path: '/system',
    name: 'system',
    meta: {
      title: '系统管理',
      icon: 'form',
      permissionArray: [1, 2, 3],
      sortIndex: 1,
      newTime: '2022-05-20'
    },
    index: 1,
    alwaysShow: false,
    component: Layout,
    children: [
      {
        path: 'menu',
        name: 'menu',
        meta: {
          title: '菜单管理',
          icon: 'form',
          permissionArray: [1, 2, 3],
          sortIndex: 2,
          newTime: '2022-05-20'
        },
        index: 2,
        component: () => import('@/views/autoRouter/system/menu.vue'),
        children: []
      },
      {
        path: 'user',
        name: 'user',
        meta: {
          title: '用户管理',
          icon: 'form',
          permissionArray: [1, 2, 3],
          sortIndex: 1,
          newTime: '2022-05-20'
        },
        index: 1,
        component: () => import('@/views/autoRouter/system/user.vue'),
        children: []
      },
      {
        path: 'role/:id',
        name: 'role_id',
        meta: {
          title: '角色管理',
          icon: 'form',
          permissionArray: [1, 2, 3],
          sortIndex: 3,
          newTime: '2022-05-20'
        },
        index: 3,
        component: () => import('@/views/autoRouter/system/role_id/index.vue'),
        children: []
      }
    ]
  }

运行结果

代码可自行npm 下载查看
https://www.npmjs.com/package/ff-auto-router

猜你喜欢

转载自blog.csdn.net/wang15180138572/article/details/120788169