likes
comments
collection
share

开学第一课:如何在vite中打造一个基于文件结构的路由系统

作者站长头像
站长
· 阅读数 17

开学第一课:如何在vite中打造一个基于文件结构的路由系统

一个较好的工程模版,不应该被较多的配置束缚住,应该有一个较好的统一约定,采用约定大于配置的方式,从而减少开发人员被配置束缚,获得简单化的同时又不失去灵活性,省去配置,减少学习成本,在前端工程中,路由配置就是一个比较麻烦的配置,那如何将前端路由系统做一个约定式开发?

通常来说,较好的约定就是文件目录结构就是路由,路由的权限以及额外配置在一个单独的文件中,next 框架就很好的实现了这一方式,他们就是采取的文件路由的方式,又或者 umi 框架,也有约定式路由的配置

开学第一课:如何在vite中打造一个基于文件结构的路由系统

通过文件结构自动生成所需要的路由,这种方式简单高效,已经成熟应用于各大框架

那如何在 vite 中实现这个功能?而不是写大量的路由配置,我们新建一个 vite 项目,然后引入 vue-router,react 也可以根据自己的方式引入对应的路由,然后对项目中的路由进行配置

一般来说,项目的路由配置是大量而且繁琐的,我们经常在项目中看到整个一套的 router 的配置,比如这种

开学第一课:如何在vite中打造一个基于文件结构的路由系统

当我需要新增一个路由的时候,需要在这个文件中编辑对应的配置,并且为了方便以后的维护,路径和文件夹一般都是一一对应的,当前的文件结构

开学第一课:如何在vite中打造一个基于文件结构的路由系统

写这种大量的配置无疑增加了我们的配置负担,而且仔细研究就会发现,这样的配置其实是通用化的,比如都有 path,而 path 和 文件目录是有对应关系的,还有一些自定义属性,包括页面的题目,菜单的顺序,权限等相关内容,或者还有可能存在 icon 之类的配置

既然 path 和文件目录有对应的关系,那我们就可以通过文件夹自动生成这份 router 配置,但是文件夹中的内容有时候可能会涉及到 components,或者一些组件的东西,这部分是不需要被映射的,同时对于一些自定义属性也要增加一些扩展

那有什么好的方式可以将这部分的东西统一收纳管理么?

文件路径可以获取,那如何避免 components 这种文件夹?同时又能够额外的增加一些配置属性呢?

这里我们可以借鉴一下微信小程序的做法,小程序是有一个 app.json 的文件,里面包含了所有页面的配置

开学第一课:如何在vite中打造一个基于文件结构的路由系统

但是我们可以针对每个页面路径下有一个独立的配置,也就是哪个文件夹你想让它成为页面就添加这个配置就可以了,我们规定它为 page.js 文件,这个文件就承担了当前文件夹页面配置效果,在 about 页面下创建这样一个 js 文件如下

开学第一课:如何在vite中打造一个基于文件结构的路由系统

然后将其它你想要当页面的文件夹下面添加 page.js 文件,接下来你要做的就是要找到对应的 page.js 文件,然后通过它生成对应的目录或者路由,当然在找的过程中,需要看你的项目是用什么东西搭建的

如果你是 webpack 的项目,你可以使用 require.context api,来获取指定的文件夹内的特定文件,可以用正则表达式去做对应的匹配,简单的用法如下

const context = require.context("./", true, /\.js$/);
console.log("keys", context.keys());

它会获取你当前目录下所有的以 .js 为后缀的文件,然后调用 keys() 的方法,就能够得到对应文件的路径,假如 ./ 下有 index.js 和 a.js 的文件,上述代码就会返回如下的数据

开学第一课:如何在vite中打造一个基于文件结构的路由系统

这是 webpack 的方式,这里我们说一下 vite 怎么获取,对应的 API 是 import.meta.glob,看一下它是干什么的

开学第一课:如何在vite中打造一个基于文件结构的路由系统

本质就是可以通过它找到对应目录下的指定的文件,也就是 page.js,我们写一下,看一下最后出来的是什么内容

// src/router.ts
const pages = import.meta.glob('./pages/**/page.js', { eager: true, import: 'default' })
// eager 表示是否直接引入对应的模块
// import default 表示直接获取到 default 的值
console.log('pages', pages)

没加 import: 'default' 的效果

开学第一课:如何在vite中打造一个基于文件结构的路由系统

加了 import: 'default' 的效果

开学第一课:如何在vite中打造一个基于文件结构的路由系统

属性名是 page.js 的路径,值就是我们配置导出的内容,这样就得到我们想要的数据了

接下来就很简单了,就是要处理这些数据,变成路由的一些配置

const pages = import.meta.glob('./pages/**/page.js', { eager: true, import: 'default' })
const routes: RouteRecordRaw[] = Object.entries(pages).map(([path, config]: [string, any]) => {
  const pathName = path.replace('./pages', '').replace('/page.js', '') || '/'
  const routeName = pathName.split('/').filter(Boolean).join('-') || 'index'
  return {
    path: pathName,
    name: routeName,
    // component 需要单独处理
    // component: () => import(path),
    meta: config
  }
})

一般来说,在 component 这一块,大家都会将 path 中的 page.js 替换成 index.vue 就可以了,但是这样会报错,主要原因是这个 import 会进行抽象语法树的分析,然后根据文件的依赖关系来进行代码的打包,但是由于这里变成了字符串,就导致依赖分析异常,它不知道我们依赖了哪些文件,所以这里这样写是有问题的

所以这里要改,改成 import.meta.glob,然后通过具体的 key value 的形式获取到我们想要的对应的文件内容

完整代码逻辑如下

const pages = import.meta.glob('./pages/**/page.js', { eager: true, import: 'default' })
const pageComponents = import.meta.glob('./pages/**/index.vue', { eager: true, import: 'default' })
const routes: RouteRecordRaw[] = Object.entries(pages).map(([path, config]: [string, any]) => {
  const pathName = path.replace('./pages', '').replace('/page.js', '') || '/'
  const routeName = pathName.split('/').filter(Boolean).join('-') || 'index'
  const componentPath = path.replace('page.js', 'index.vue')
  return {
    path: pathName,
    name: routeName,
    // component 需要单独处理
    component: () => pageComponents[componentPath],
    meta: config
  }
})

这样就能够对文件夹自动生成路由模式,不需要手动添加对应的路由配置,减少了配置的风险,并且对每个页面配置单独抽离,不相互影响,较好的解决了较长的配置文件问题,减少出错