地球上没有对手的 SSR 框架——使用回溯搞定约定式路由

地球上没有对手的SSR框架,这是一个非常优秀的框架,核心代码量很少,相较于 Next.js 几十万行代码而言,学习难度要小很多。所以对 ssr 感兴趣的小伙伴,可以从这个框架开始你的 ssr之旅~

今天我们先来讲讲约定式路由

什么是约定式路由

约定式路由即根据前端文件夹结构来自动的生成前端路由配置,无需手动配置路由信息的方法,通常需要约定好项目文件的结构,开发者按照约定来创建文件。这种方法使得开发者无需手动编写大量的路由配置,简化了路由管理的复杂性。

举个例子,现在有如下文件夹结构,会生成怎样的路由呢?

pages                
├─ detail            
│  ├─ fetch.ts       
│  └─ render$id.tsx  
└─ index             
   ├─ fetch.ts       
   └─ render.tsx     

我们约定 pages 文件夹下存放的每一个文件代表一个页面,同时,我们约定 render 文件代表渲染这个页面的组件。

一个页面的路由有多种形式:

  • 普通路由即 //detail 这种。对于 /index/render.tsx,最终就会映射为 /index 不会保留。
  • 动态路由即 /detail/:id 这种后面是动态的参数的形式的路由。/detail/render$id.tsx 会映射为 /detail/:id,如果是 /detail/render$id$bar.tsx 会映射为 /detail/:id/:bar
  • 可选参数路由即 url 中带有 query 参数形式,比如 /detail?a=b,由于 ? 符号无法作为文件名使用,所以该框架使用了 # 代替。/detail/render$id#.tsx 会映射为 /detail/:id?
  • 多级路由即 /user/detail 这种,对于 /user/detail/render.tsx 就会映射为 /user/detail

约定式路由的实现

接下来我们讲一下约定式路由的实现,代码量很少,核心就是一个回溯算法!

下面给出源码实现,删减了一些目前无需关注的代码,连 50 行都不到!

const renderRoutes = async (pageDir, pathRecord, route) => {
    let arr = []
    const pagesFolder = await fs.readdir(pageDir)
    const prefixPath = pathRecord.join('/')
    const aliasPath = `@/pages${prefixPath}`
    const routeArr = []
    for (const pageFiles of pagesFolders) {
        const abFolder = path.join(pageDir, pageFiles)
        const isDirectory = (await fs.stat(abFolder)).isDirectory()
        if (isDirectory) {
          // 如果是文件夹则递归下去, 记录路径
          pathRecord.push(pageFiles)
          const childArr = await renderRoutes(abFolder, pathRecord, Object.assign({}, route))
          pathRecord.pop() // 回溯
          arr = arr.concat(childArr)
        } else {
          // 遍历一个文件夹下面的所有文件
          if (!pageFiles.includes('render')) {
            continue
          }
          // 拿到具体的文件
          if (pageFiles.includes('render$')) {
            // 非普通路由 
            /* /news/:id */
            route.path = `${prefixPath}/:${getDynamicParam(pageFiles)}`
            route.component = `${aliasPath}/${pageFiles}`
          } else if (pageFiles.includes('render')) {
            // 普通路由
            /* /news */
            route.path = `${prefixPath}`
            route.component = `${aliasPath}/${pageFiles}`
          }
      }
    
      routeArr.forEach((r) => {
        if (r.path?.includes('index')) {
          // /index 映射为 /
          if (r.path.split('/').length >= 3) {
            r.path = r.path.replace('/index', '')
          } else {
            r.path = r.path.replace('index', '')
          }
        }

        r.path && arr.push(r)
      })

      return arr
}
  1. 函数接收三个参数,pageDir 代表所有页面的目录路径,首次传入是 pages 目录所在的路径;pathRecord是一个数组用来记录页面路径;route 是存储页面路由的对象

  2. 首先读取 pageDir 目录下的所有文件和文件夹,然后根据传入的 pathRecord 数组拼接出 prefixPath(前缀路径)和 aliasPath(别名路径)

  3. 遍历 pageDir 目录下的所有文件和文件夹

    1. 如果是文件夹,则递归调用 renderRoutes 函数,同时更新 pathRecord
    2. 如果是文件,并且不包含 render 命名的文件,则跳过,表示没有要渲染的页面。如果包含,则去查看是否包含 render$ 开头命名的文件,如果有则会调用 getDynamicParam 方法对文件名进行一个解析,解析出动态路由、可选参数路由、通配符路由;如果没有以 render$ 开头命名的文件,则判断是否有以 render 开头的文件,即普通路由。
  4. 最后其实也是对路由做了一点转化,如果 pages 下有以 index 命名的文件夹,会转成空字符串。举个例子,我们页面统一的前缀是 /srr 那么 /pages/index/render.tsx 最后会被映射成的路由就是 /ssr,而 /pages/detail/render.tsx 会被映射成路由 /ssr/detail

OK,解析约定式路由的核心代码就这么少,其实就是一个递归文件夹的回溯。

总结

本文讲解了约定式路由的实现,在 Next.js、Umi 等框架中,都有约定式路由的身影,只要我们知道了框架中它是如何约定路由的,按照它的约定去创建文件结构,能够极大简化我们管理路由的负担。

同时,从约定式路由这一点就能看出,该框架的实现就是极简的,也就 2000+ 的代码就实现了一个可在实际项目中使用的 ssr 框架,绝不是很多文章写的 demo 实现。

原文链接:https://juejin.cn/post/7349087888048586787 作者:ShowBuger

(0)
上一篇 2024年3月23日 上午10:00
下一篇 2024年3月23日 上午10:10

相关推荐

发表回复

登录后才能评论