likes
comments
collection
share

在Vite中使用glob完成自动生成Router规则

作者站长头像
站长
· 阅读数 10
  • 我们在工作当中,其实会遇到很多的体力活,它没有什么技术含量,但你还不得不做,比方说像这个路由配置:

    import { createRouter, createWebHashHistory } from 'vue-router'
    const routes = [
      {
        path: '/',
        name: 'index',
        component: () => import('../views/index.vue'),
        meta: { title: '首页', menuOrder: 1 }
      },
      {
        path: '',
        name: '',
        component: () => import('../views/about/index.vue'),
        meta: { title: '关于', menuOrder: 1 }
      },
      {
        path: '',
        name: '',
        component: () => import('../views/contact/index.vue'),
        meta: { title: '联系', menuOrder: 1 }
      },
    ]
    export const router = createRouter({
      history: createWebHashHistory(),
      routes
    })
    
  • 有没感觉路由配置就是一个纯粹的体力活,但你又不得不干的体力活?

  • 如果你的心思足够细腻,你还会感觉到这个玩意儿有点像重复代码,但是你又说不好哪里重复了。你的感觉是非常正确的,它就是重复代码,它跟谁重复了呢?跟我们的目录结构重复了。看看对比:

    在Vite中使用glob完成自动生成Router规则

  • 为什么要把它写一遍呢?

  • 其实我们软件工程里边早就出现了一个概念,叫做:约定大于配置,就是在我们实际开发中,公司往往会给予很多的开发规范,在这些规范里边其实就已经暗含了配置了

  • 比如about页面,需要建一个文件夹,它的页面组件是/views/about/index.vue,contact页面也同理。按照这样的规范去写出来的目录,它其实就是路由配置

  • 那我现在要想一想了,既然如此啊,我们能不能找到一个办法,就是在代码里边自动帮我们生成路由配置,不用去手动写;

  • 手动写的还经常写错,单词写错,大小写写错,然后呢,导入路径写错,增加了不少的错误几率,而且将来这个目录结构一改,如果说你这里忘记了更改,还要造成很多的隐患,那么如果说能够自动生成的话,这些问题都不复存在了,那怎么来做呢?

  • 此时我想,能否对/router/index.js文件内定义的routes这个路由配置做文章?

    在Vite中使用glob完成自动生成Router规则

  • 参考小程序的做法,为每一个页面呢写上一个配置,用json或js都行,导出一个配置meta中缺失的配置

    export default {
      title: 'Home',
      menuOrder: 1
    }
    
    
  • 同样的道理呢,我们把这个配置呢,应用到每一个页面,每一个页面带一个配置,换句话说,只要有这个配置,它就是一个页面,没有这个配置它就不是一个页面,

  • 在Vite中使用glob完成自动生成Router规则

  • 这么配置后,当每次新建页面,把这个配置给它带上就完事了。这么一来,信息就完整了,我们就可以大胆的不再使用手动的书写方式了啊,为了避免报错呢,暂时的给/router/index.js文件内导出的routes配置为一个空数组,然后将routes自动生成

  • 怎么生成?如果说你用的是vue-cli,由于它背后使用了Webpack,那么你可以利用里边的**require.context()**来进行批量导入,用它可以把views文件夹下边的所有你需要的文件都一起导入进来,比方说你要找到它里边的所有page.js可以全部导入进来。

  • 由于我这里用的是viet,它背后不是Webpack,上述方法用不了。在vite里边呢,它也有相同的方式来做同样的事情,这种方式呢,叫做glob

  • 通过查看官方文档发现,这是一个函数,给它传入一个匹配规则,它就能找到这个文件夹下边的所有的相应文件,把这些文件导入进来,生成一个模块的集合。

  • 比方说我要匹配不views文件夹下边的所有的page.js的文件,

    const pageModules = import.meta.glob('../views/**/page.js')
    console.log(pageModules)
    

    在Vite中使用glob完成自动生成Router规则

  • 观察打印结果发现,对象的属性名是对应的路径,值为动态导入函数,这些就是我们想要的配置啊

  • 如果你希望对象的值不是动态导入的函数,官方文档上有一个globEager方法

      ```javascript
      const pageModules = import.meta.globEager('../views/**/page.js')
      ```
      
    
  • 在或者你指向要这个模块集合里的ddefault对象,那就稍微有点复杂了,不过,抱着死磕到底的心态,绝对尝试下,结果真给我搞出来了。下面是具体实现代码:

    const pages = Object.fromEntries(Object.entries(pageModules).map(([path, pageModule]) => [path, pageModule.default || {}]))
    
  • 首先,我们使用Object.entries将模块对象pageModules转换为一个键值对数组,其中每个键值对都包含模块路径作为键和模块对象作为值。

  • 接下来,我们使用map方法对这个数组进行遍历,对于每个键值对,我们都将其转换为一个包含模块路径作为键和模块对象default属性值作为值的键值对数组。这里使用了解构语法将pathpageModule分别指定为数组项的第一个和第二个元素,然后使用数组字面量的方式返回一个包含pathpageModule.default的新数组。

  • 最后,我们使用Object.fromEntries将新数组转换为一个对象字面量,其中每个键值对都由原来的模块路径和default属性值组成。由于一些模块可能没有default属性值,所以我们使用|| {}语法将其设置为空对象,以防止在后续操作中出现undefined值的情况。

  • 这样就处理好了我想要的数据,打印出来看下:

    在Vite中使用glob完成自动生成Router规则

  • 再来观察下这个对象,有3个对应的路径的属性和模块内容,说明我们应该有三个对应的路由配置。

  • 这里通过 Object.entries() 方法将pages转为数组:

    const routes = Object.entries(pages)
    

    在Vite中使用glob完成自动生成Router规则

  • 接下来需要通过数组的map方法,将数组内的每一项,映射成路由配置,每一次映射,都要返回一个路由配置,比如下面这个:

      return {
        path: '/',
        name: 'home',
        component: () => import('../views/index.vue'),
        meta: { title: 'Home', menuOrder: 1 }
      }
    
  • 先找简单的,观察通过数组内的成员,config这个配置对应的就是meta对象,替换即可

      return {
        path: '/',
        name: 'home',
        component: () => import('../views/index.vue'),
        meta: config
      }
    

    在Vite中使用glob完成自动生成Router规则

  • 接下来是路径,要把../views/about.page.js这个数组成员截取头尾,只留下/about,路径就出来了,首页的路径需要做点出来,以下是实现代码:

  • const routes = Object.entries(pages).map(([pagePath, config]) => {
      const path = (pagePath.replace('../views', '').replace('/page.js', '') || '/')
      return {
        path,
        name: 'home',
        component: () => import('../views/index.vue'),
        meta: config  // config 就是 meta 的值,直接就能用
      }
    })
    

    效果:

    在Vite中使用glob完成自动生成Router规则

  • 然后是路由name,具体看公司规定了,这里就简单点吧,比如/about,就将其命名为about

    const routes = Object.entries(pages).map(([pagePath, config]) => {
      const path = (pagePath.replace('../views', '').replace('/page.js', '') || '/')
      const name = path.split('/').filter(Boolean).join('-') || 'home'
      return {
        path,
        name,
        component: () => import('../views/index.vue'),
        meta: config
      }
    })
    

    看效果:

    在Vite中使用glob完成自动生成Router规则

  • 最后是component,数组对象内貌似有这个玩意,想这直接用 replace方法替换,结果运行时编辑器终端报错。。。

    const routes = Object.entries(pages).map(([pagePath, config]) => {
      const path = (pagePath.replace('../views', '').replace('/page.js', '') || '/')
      const name = path.split('/').filter(Boolean).join('-') || 'home'
      const compPath = pagePath.replace('page.js', 'index.vue')
      return {
        path: path,
        name,
        component: () => import(compPath),
        meta: config
      }
    })
    

    在Vite中使用glob完成自动生成Router规则 component对应的属性值变成了经过vite转换后的字面量,这不是我们想要的。 在Vite中使用glob完成自动生成Router规则

  • 查阅资料后发现,因为vite是用rollup进行打包的,在这种环境内打包,有一个要求,就是import内不能放变量,要放字面量,不然会影响到其的静态分析。

  • 这里想到在导入页面模块的时候,再去导入组件的模块,在import里就有我们想要的字面量了,这下就简单了,只需去取组件模块内的import字面量,替换到map方法内,就行了。

    在Vite中使用glob完成自动生成Router规则

    const routes = Object.entries(pages).map(([pagePath, config]) => {
      const path = (pagePath.replace('../views', '').replace('/page.js', '') || '/')
      const name = path.split('/').filter(Boolean).join('-') || 'home'
      const compPath = pagePath.replace('page.js', 'index.vue')
      return {
        path: path,
        name,
        component: compModules[compPath],
        meta: config
      }
    })
    
  • 再来看下数据结构: 在Vite中使用glob完成自动生成Router规则

  • 最后,把先去为防止报错而赋值成空数组的routes去掉,大功告成了,附上/router/index.js文件完整代码:

    import { createRouter, createWebHashHistory } from 'vue-router'
    
    // const routes = [
    //   {
    //     path: '/',
    //     name: 'home',
    //     component: () => import('../views/index.vue'),
    //     meta: { title: 'Home', menuOrder: 1 }
    //   },
    //   {
    //     path: '/about',
    //     name: 'about',
    //     component: () => import('../views/about/index.vue'),
    //     meta: { title: 'about', menuOrder: 10 }
    //   },
    //   {
    //     path: '/contact',
    //     name: 'contact',
    //     component: () => import('../views/contact/index.vue'),
    //     meta: { title: 'contact', menuOrder: 100 }
    //   },
    // ]
    
    const pageModules = import.meta.globEager('../views/**/page.js')
    const pages = Object.fromEntries(Object.entries(pageModules).map(([path, pageModule]) => [path, pageModule.default || {}]))
    const compModules = import.meta.glob('../views/**/index.vue')
    const routes = Object.entries(pages).map(([pagePath, config]) => {
      const path = (pagePath.replace('../views', '').replace('/page.js', '') || '/')
      const name = path.split('/').filter(Boolean).join('-') || 'home'
      const compPath = pagePath.replace('page.js', 'index.vue')
      return {
        path: path,
        name,
        component: compModules[compPath],
        meta: config
      }
    })
    
    export const router = createRouter({
      history: createWebHashHistory(),
      routes
    })
    
  • 页面效果: 在Vite中使用glob完成自动生成Router规则

  • App.vue完整代码:

    <script setup>
    import { watchEffect, ref } from 'vue'
    import { router } from './router'
    const routes = ref(router.getRoutes())
    watchEffect(() => (routes.value = router.getRoutes))
    const onUpdated = () => (routes.value = router.getRoutes())
    router.afterEach(onUpdated)
    </script>
    
    <template>
      <nav>
        <router-link v-for="route in routes" :key="route.path" :to="route.path">{{ route.meta.title }}</router-link>
      </nav>
      <router-view></router-view>
    </template>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    nav {
      text-align: center;
    }
    nav > a {
      margin-left: 20px;
      text-decoration: none;
    }
    </style>
    
    
  • 在此基础上,如果需要新增页面的时候,只需在views文件夹加内新建对应的页面文件并带上page.js配置文件,就可不用再手动配置路由了。

    在Vite中使用glob完成自动生成Router规则