最近因为毕设需要,又重温了一下前端的屎山,毕设是做个前后端分离的商城类项目,前台我是准备用 SolidJS 写,后台管理界面用 vue3。一开始是觉得前台业务逻辑一般不会太过复杂,忘了这是商城了,用户购物车、订单管理什么的,逻辑其实也挺复杂的。不过无所谓了,冲!!!
为什么要基于文件系统路由
其实也是在 vite 找 SolidJS 的社区模板的时候看到了这个说法,第一反应就是之前学 vue3 看的一个后台管理的开源项目的源码,里面用到了 import.meta.glob
来读取目录下的路由文件。但仔细看了一下,区别很大。
这里 SolidJS 的文件路由,主要指的是 solid-start 这个库里的基于文件的路由(详细可见:Routing (solidjs.com)),简单来说,就是我们在创建路由时,只需要在 routes 文件夹下新建 view 模板,系统会根据文件名和路劲来确定路由:
hogwarts.com
➜/routes/index.tsx
hogwarts.com/blog
➜/routes/blog.tsx
hogwarts.com/admin/edit-settings
➜/routes/admin/edit-settings.tsx
并且也提供了通过在 tsx 文件了 export route 对象的方式来对路由进行其他配置,可以看到,这种做法使得项目的路由配置非常清晰高效。solid-start 的源码进去看了一下,没找到具体实现的代码在哪里,于是就尝试用 import.meta.glob
来实现该功能
import.meta.glob 介绍
import.meta.glob
是 vite 提供的一个从文件系统导入多个模块的函数,如果你不设置 {eager: true}
的话,它会自动转译成异步加载代码,我们的路由业务肯定是需要异步加载来进行分包的,所以这里就不设置 eager
了。
// 获取 routes 目录下的所有模板对象
const routeViews = import.meta.glob<any>('./routes/**/*.tsx')
console.log(routeViews)
// 会生成这样的东西
{
"./routes/index.tsx": () => import("/src/routes/index.tsx")
"./routes/shop/index.tsx": () => import("/src/routes/shop/index.tsx")
}
预处理获取到的数据
我们得到了 routeViews
之后,就需要对其进行预处理了,主要是将文件路径转化为路由的 path
注意这里我没有考虑 param 的情况,你可以对照 solid-start 的规则,用 [id] 之类的命名方式来识别路径参数,实现起来也很简单
function handleRouteViews() {
const routes: RouteDefinition[] = []
for (const path in routeViews) {
// 这里的 component 就是上面的 import 异步函数
const component = routeViews[path]
// 替换路径
let pathNew = path.replace('./routes/', '').replace('.tsx', '')
if (pathNew.endsWith('index')) {
pathNew = pathNew.replace('index', '')
}
const route = {
path: pathNew,
// 这里使用 SolidJS 的 lazy 函数来包装我们的异步导入
component: lazy(component),
}
routes.push(route)
}
return routes
}
这样我们就根据文件路径获取到了路由路径和组件本身,但这显然还不够,因为实际业务中,我们在路由跳转的时候可能还需要进行一些工作,比如设置标题,准备数据之类的,SolidJS 的路由提供了 load 参数来实现跳转的时候进行数据获取和其它操作。
获取组件的路由配置
我们模仿 solid-start ,通过在组件中导出配置对象的方法来获取对应组件的配置(这里统一 export const route),按照以上的代码,不难想到通过 component.route
来获取目标对象,但 component 是一个异步函数,那我们又该如何在保证组件不会预先加载的情况下获取到对象呢?
一开始我想到的使用 import.meta.glob
的 eager
和 import
配置了具名导出该对象,但这种做法会导致 vite 不再将组件分包,也就是失去按需加载的能力,这无疑是致命的。后来又想到 vue 的那些开源的项目多是将路由配置与模板文件分开存放,就也想那么做,但感觉很鸡肋,又回到了老的路由配置模式。
想了半天,突然想到,load 本身就是一个函数,而且是支持异步的,直接将 component 放进 load 里不就好了?
{
load: () => {
component().then((mod: any) => {
const routeConf = mod.route
if (routeConf?.title) {
document.title = `${routeConf.title} - 特色农产品团购平台`
}
routeConf?.load?.()
})
},
}
测试,成功!
总结
其实后面我还是很担心这种写法是不是会导致分包问题,经过测试,正确的做到了分包和懒加载。理论上,只要是 vite 应该就能这么做,无非是导出的路由配置项有差异,当然由于我没找到相关资料,啃源代码的能力也不太行,不清楚我的写法是否规范,有没有隐患。