Fwio

借助 import.meta.glob 编写自动路由脚本

脚本源码:auto-routes.ts

背景

自动路由,也就是自动读取指定目录下的文件,为它们生成指定环境下的路由对象的功能。

Flog(即这个网站)一开始是借助 vite-plugin-pages 这样一个集成插件来实现自动路由的。 为了写 Flog 与 SSG 这篇博客,我去查阅了一些 SSG 框架的文档,其中在 VitePress 的官方文档中得知其自动路由是仅通过一个脚本实现的。

VitePress 的路由方案

当然,VitePress 想强调的是它没有使用 Vue Router 这样集成的依赖,而是通过原生的 history 对象或其他 API 实现了更为轻量级的路由。 但我从这里受到的启发是,我也可以编写一个脚本,在不使用vite-plugin-pages这样的集成插件的前提下,去自动地生成路由。

项目中,这个脚本的基本需求是:

  • 读取pages目录下的所有.mdx.tsx文件。
  • 从文件对象中取得文件路径和其导出的 React 组件。
  • 根据目录路径和组件对象,将它们转换成一个可以由useRoute()调用的路由对象数组(Array<RouteObject>)。

这个脚本主要由两个功能构成:

  1. 读取指定目录文件:Vite 为资源加载提供了 import.meta.glob API,只需为其提供目标文件的glob pattern, 它就能读取符合条件的文件,如果目标是 JS 文件,更能将其作为 ES Module 解析获取其export
  2. 从文件到路由import.meta.glob所返回的文件结构是扁平(flat)的,我们需要将其转化成 RouteObject 这样的树形结构children?: RouteObject[])。

第一点通过import.meta.glob可以很轻松地解决,那么主要的难点好像就成了一道算法题,如何将数组转化为树

将数组转为树

用外部变量result: RouteObject[]存储最终结果,算法主要用到栈的思想,大致思路如下:

  1. 根据文件路径path,将组件数组({ element: FC, path: string}[]排序import.meta.glob返回的结果已是有序);
  2. 在循环外,使用一个外部栈型变量currDirs记录上一个已处理组件的父路由(即包含children属性的路由);
  3. 遍历文件数组:
    1. 对每一个文件,用其path从前往后匹配currDirs,记录最大匹配索引:
      • 匹配成功:不作特殊处理;
      • 部分匹配或匹配失败:将currDirs栈顶层匹配失败的路由对象出栈,如果currDirs清空,将其栈底元素推入result中。
    2. 根据该文件的path继续(重新)构建currDirs;
    3. 为文件生成RouteObject,推入栈顶父路由的children中。

打包体积对比

Before:

Before

After:

After

可以看到,移除vite-plugin-pages后,应用的打包体积优化了整整…64 KB