webpack automatic routing plugin

Due to the automatic routing of the previously built single-page architecture, the configuration of meta information requires a separate configuration file, and only the configuration supports second-level routing, and third-level routing needs to be generated by handwritten configuration. Dynamic routing also needs to configure id. Not very user friendly. Now I want to use the form of webpack plug-in to automatically generate routing files, configure meta information in each vue page, support multi-level routing, and optimize routing code. (There are also some automatic routing plug-ins on the Internet, but they are not suitable for accessing the single-page architecture written before, so I wrote one myself)

the whole idea

1. Write a webpack plug-in to generate the routes.js file when the code is running.
2. Pass in the directory where the file needs to be generated when the code is running, and output the directory path of routes.js.
3. According to the directory path (system/user/index.vue ) to generate a nested tree

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

4. According to the nested tree, recursively traverse the configuration routing information to generate json data
5. Nodejs reads the meta information in the vue file in the directory and integrates it into the routing 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. Recursively match the route json data to generate the string data of routes.js
7. Use nodejs to write the routes.js file in the output directory

Code

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

1. Define an automatic routing plug-in class, define a method in this class to realize directory path input, and routes.js file output data. in the write file

// 定义一个自动路由插件类
    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. Generate a nested tree from the directory

/**
 * 根据目录去生成路由
 * @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. Recursively process the nested tree to generate routing json data, and read the meta information in the vue page.
Note: To generate nested routing, a layout file is required in the directory. Another function for configuring the meta information of the parent node is when When the route is larger than the second-level route, _layout needs to be present, otherwise the page will not be displayed

/**
 *
 * @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. Match the routing json data to generate string data in the routes.js file


/**
 * 将路由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}]
  }`
}

How to use

1. Import

npm install  ff-auto-router

2. Modify 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 The directory where files need to be automatically generated
importPrefix import Introduce the prefix directory of page files
routePath The file directory generated by routing, if set, the routing file will be generated in the specified directory of the current project, otherwise it can be imported from ff-auto-routeff-auto-router/lib/routes

metaAmong them , the vue-routerconfigured metaproperties are consistent
and written in the export default {} of each component

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

3. Modify the src/router/index.js file.
The configuration here is written according to the actual situation of the project. This is the basic way of writing can be used

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

Display of results

directory file

{
    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: []
      }
    ]
  }

operation result

The code can be downloaded from npm to view
https://www.npmjs.com/package/ff-auto-router

Guess you like

Origin blog.csdn.net/wang15180138572/article/details/120788169