Published on

ssg - 约定式路由

前言

最近开了一个新坑, 搭建简易 ssg,对标 vitepress 正在研发核心功能,约定式路由,此篇介绍一下具体实现

Convention Routing

首先会读取用户提供的 config,在结合一些通用的配置,返回一个新的 config, 这样可以做到开箱即用。

const resolveConfig = async (...) => {
  // 导入用户配置文件 mmc.config.ts
  const userConfig = await resolveUserConfig(...)

 const {
    extensions = ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
    exclude = [],
    include = []
  } = userConfig.route ?? {}

  // 获取根目录下指定文件
  const files = (
    await fg([`**/*.{${extensions.join(',')}}`, ...include], {
      cwd: root,
      absolute: true,
      ignore: [...DEFAULT_EXCLUDE, ...exclude],
    })
  ).sort()

  // 做一些路径上的处理 形成约定式路由
  const pages = files.map((file) => ...)
}

这里用到了几个关键的函数

// 获取用户提供的 config.ts 配置文件
resolveUserConfig

// 这是一个非常快速和高效的Node.js glob库。
// https://github.com/mrmlnc/fast-glob
import fg from 'fast-glob'

Plugins

在服务启动时,将获取到的 config 传入 每个 plugin

这里介绍 路由插件,在服务启动时,获取约定式路由,计算一次,在任何地方使用

// vite
export const pluginRoutes = (config: SiteConfig): Plugin => {
  const virtualModuleId = 'virtual:routes'
  const resolvedVirtualModuleId = `\0${virtualModuleId}`

  return {
    name: 'vite-plugin-routes',
    resolveId(id) {
      if (id === virtualModuleId) return resolvedVirtualModuleId
    },

    load(id, options) {
      if (id === resolvedVirtualModuleId) {
        // 做不同的处理
        console.log(options?.ssr ? 'ssr' : 'not ssr')
        return {
          // 转换 文件内容
          code: generateRoutesCode(config),
          moduleSideEffects: false,
        }
      }
    },
  }
}

我这里将是读取了文件,并且转换成 react 组件的形式 (需要对应的 loader),可以直接渲染到页面中

export const generateRoutesCode = (options: SiteConfig) => {
  const { pages = [] } = options

  const namePrefix = 'Page'

  return `
    import React from 'react'
    ${pages
      .map(
        (route, index) =>
          `const ${namePrefix}${index} = React.lazy(() => import('${route.filePath}'))`
      )
      .join('\n')}
    export const routes = [
      ${pages
        .map(
          (route, index) => `{
        path: '${route.path}',
        element: React.createElement(${`${namePrefix}${index}`}),
      }`
        )
        .join(',\n')}
    ]`
}

在其他地方使用

import routes from 'virtual:routes'