我不知道的 dumi:自动路由生成的原理解析

380 字 7 min read
前端开发 dumi Umi 自动路由 Markdown

dumi 是基于 Umi 的组件文档工具,其自动路由生成机制令人称奇——只需放置 Markdown 文件,路由即刻生成,无需手动配置。这一“魔法”如何实现?本文将从 dumi 的约定式路由设计入手,深入揭开文件到路由的转换流程,分析元数据解析与多级嵌套处理,探索路由与组件预览的联动及扩展实践。这是一场从原理到应用的完整旅程,带你理解 dumi 的路由生成奥秘。🚀


1. dumi 约定式路由系统的设计原则

dumi 的路由系统遵循“约定大于配置”的设计哲学,旨在简化文档开发流程。

  • 核心原则
    • 文件即路由:目录结构直接映射为路由路径。
    • 简单直观:文件命名与位置决定路由,无需复杂配置。
    • 灵活扩展:支持多语言等高级需求。
    • 组件兼容:无缝集成文档与组件预览。
  • 实现基础
    • dumi 通过 @umijs/preset-dumi 扩展 Umi 的路由功能,扫描指定目录(如 docs/)。
    • 细节:配置 resolve.includes 指定扫描范围。
    • 示例
      // .umirc.ts
      import { defineConfig } from 'dumi';
      export default defineConfig({
        resolve: { includes: ['docs'] },
      });
      

设计逻辑:约定式路由以直观性为核心,奠定自动生成的基础。


2. 从文件到路由的转换流程详解

dumi 将 Markdown 文件转为路由的流程包含多个阶段。

  • 转换流程
    1. 文件扫描:扫描 resolve.includes 指定的目录,识别 .md 文件。
    2. 路径分析:提取相对路径,规范化路由(如 index.md 转为 /)。
    3. 元数据解析:读取 frontmatter,提取路由信息。
    4. 路由生成:创建路由对象,绑定组件。
    5. 输出:生成静态 HTML 与导航。
  • 实现细节
    • 扫描:使用 Umi 的 glob 模块扫描文件。
    • 伪代码
      function scanFiles(dir) {
        const files = glob.sync(`${dir}/**/*.md`, { ignore: ['**/node_modules/**'] });
        return files.map(file => ({
          path: normalizePath(file),
          content: readFile(file),
        }));
      }
      
    • 示例
      docs/
      ├── index.md       -> /
      ├── guide.md       -> /guide
      └── components/
          └── button.md  -> /components/button
      

转换逻辑:文件路径与元数据结合,自动生成路由表。


3. 元数据解析与路由配置生成机制

dumi 通过解析 Markdown 文件的 frontmatter 生成路由元数据。

  • 元数据解析
    • 原理:使用 gray-matter 解析 YAML 头部。
    • 细节@umijs/preset-dumiremark-frontmatter 插件提取字段。
    • 示例
      <!-- docs/guide.md -->
      ---
      title: Guide Page
      order: 1
      nav: Guide
      ---
      # Welcome
      
  • 路由配置生成
    • 机制:将元数据注入 Umi 的 IRoute 对象。
    • 细节:通过 api.modifyRoutes 扩展路由。
    • 示例
      // 路由对象
      {
        path: '/guide',
        component: 'docs/guide.md',
        exact: true,
        meta: { title: 'Guide Page', order: 1, nav: 'Guide' }
      }
      

生成逻辑:元数据驱动路由配置,提升导航灵活性。


4. 多级嵌套的处理策略

dumi 支持多级嵌套路由,适应复杂文档结构。

  • 多级嵌套
    • 原理:子目录自动转为嵌套路由,index.md 作为默认入口。
    • 细节:Umi 的 IRoute 接口支持嵌套层级,dumi 扩展导航树。
    • 示例
      docs/
      └── guide/
          ├── index.md    -> /guide
          └── advanced.md -> /guide/advanced
      
  • 优先级
    • 精确路径优先于较长的嵌套路径。
    • order 值小的路由优先展示。

处理策略:嵌套路由支持复杂导航需求。


5. 路由与组件预览的联动实现

dumi 将路由与组件预览联动,提升交互体验。

  • 联动机制
    • 原理:Markdown 中的 JSX 代码块通过 @umijs/preset-dumi 转为可交互组件。
    • 实现sucrase 编译 JSX,客户端渲染。
    • 伪代码
      function renderPreview(code, meta) {
        if (meta.type === 'preview') {
          const compiled = sucrase.transform(code, { transforms: ['jsx'] });
          return <div>{eval(compiled.code)()}</div>;
        }
        return <pre>{code}</pre>;
      }
      
    • 示例
      <!-- docs/button.md -->
      # Button
      ```jsx
      export default () => <button>Click me</button>;
      
      生成路由 `/button`,包含交互按钮。
      
  • 细节:路由页面通过 dumi 的渲染器加载预览组件。

联动逻辑:路由驱动页面,组件预览增强交互性。


6. 扩展路由支持多语言与动态内容的实践

dumi 的路由系统支持扩展,适应多语言与动态内容。

  • 多语言支持
    • 原理:通过语言后缀(如 .en.md)区分版本。
    • 实践
      // plugin-i18n.js
      export default (api) => {
        api.modifyRoutes((routes) => {
          const enRoutes = routes.map(route => ({
            ...route,
            path: `/en${route.path}`,
            meta: { ...route.meta, lang: 'en' },
          }));
          return [...routes, ...enRoutes];
        });
      };
      
    • 配置
      // .umirc.ts
      export default { plugins: [require.resolve('./plugin-i18n.js')] };
      
    • 文件结构
      docs/
      ├── guide.md      -> /guide
      └── guide.en.md   -> /en/guide
      
  • 动态内容
    • 原理:通过 api.onGenerateFiles 生成路由。
    • 示例
      export default (api) => {
        api.onGenerateFiles(() => {
          api.writeTmpFile({
            path: 'dumi/dynamic.tsx',
            content: 'export default () => <div>Dynamic</div>;',
          });
        });
        api.modifyRoutes((routes) => [
          ...routes,
          { path: '/dynamic', component: 'dumi/dynamic.tsx' },
        ]);
      };
      

扩展逻辑:插件化扩展支持多语言与动态内容。


总结:从约定到扩展的旅程

dumi 通过约定式路由,将 Markdown 文件转为高效文档导航。设计奠基,流程优化,元数据驱动,嵌套与预览联动,最终形成扩展闭环。未来,dumi 可通过 AI 优化路由生成,或支持更复杂场景。理解这一过程,你会更从容地构建文档路由。下次编写文档时,想想这背后的幕后逻辑吧!💡