我不知道的 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'] }, });
- dumi 通过
设计逻辑:约定式路由以直观性为核心,奠定自动生成的基础。
2. 从文件到路由的转换流程详解
dumi 将 Markdown 文件转为路由的流程包含多个阶段。
- 转换流程:
- 文件扫描:扫描
resolve.includes
指定的目录,识别.md
文件。 - 路径分析:提取相对路径,规范化路由(如
index.md
转为/
)。 - 元数据解析:读取 frontmatter,提取路由信息。
- 路由生成:创建路由对象,绑定组件。
- 输出:生成静态 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
- 扫描:使用 Umi 的
转换逻辑:文件路径与元数据结合,自动生成路由表。
3. 元数据解析与路由配置生成机制
dumi 通过解析 Markdown 文件的 frontmatter 生成路由元数据。
- 元数据解析:
- 原理:使用
gray-matter
解析 YAML 头部。 - 细节:
@umijs/preset-dumi
的remark-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' } }
- 机制:将元数据注入 Umi 的
生成逻辑:元数据驱动路由配置,提升导航灵活性。
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`,包含交互按钮。
- 原理:Markdown 中的 JSX 代码块通过
- 细节:路由页面通过 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 优化路由生成,或支持更复杂场景。理解这一过程,你会更从容地构建文档路由。下次编写文档时,想想这背后的幕后逻辑吧!💡