渲染器架构
Renderer 是 Clarify 的客户端运行时。它消费 @clarify-labs/cli 内部引擎生成的已处理内容(路由、MDX 组件、API 数据),并将其渲染为交互式文档站点。
渲染理念
- 静态优先 + 客户端 Hydration:构建时生成 HTML,浏览器端再 Hydration 为 React Router 应用
- 静态构建时数据:CLI 在构建时生成路由清单、导航树、配置和 OpenAPI 注册表。渲染器在运行时消费这些静态产物
- 组件驱动 UI:MDX 元素映射到
@clarify-labs/renderer原语,应用壳负责导航、搜索、主题切换和内容操作 - 零运行时配置解析:所有配置在构建时解析并烘焙到虚拟模块中
运行时架构
客户端入口
CLI 生成 virtual:clarify-entry-client,并在 HTML 中注入该入口。入口会导入渲染器样式、虚拟模块数据并调用 render:
import '@clarify-labs/renderer/style.css'
import { render } from '@clarify-labs/renderer/client'
import { routes, navigation } from 'virtual:clarify-routes'
import { config } from 'virtual:clarify-config'
import { openApis } from 'virtual:clarify-openapi-registry'
render({ config, routes, navigation, openApis })
用户项目不需要维护 source/main.tsx 或自定义 Vite 入口。
服务端入口
静态生成阶段使用临时 SSR 入口调用 renderToHTML:
import { renderToHTML } from '@clarify-labs/renderer/server'
import { routes, navigation } from 'virtual:clarify-routes/server'
import { config } from 'virtual:clarify-config'
import { openApis } from 'virtual:clarify-openapi-registry'
export function render(url: string) {
return renderToHTML({ config, routes, navigation, openApis, url })
}
应用壳
AppShell 是渲染器的顶级壳,接收配置、路由和导航树:
<AppShell config={config} routes={routes} navigation={navigation} />
布局区域:
| 区域 | 职责 | 固定? |
|---|---|---|
Header | 搜索、顶部导航、语言切换、主题切换 | 是(顶部) |
Navigation | 来自 navigation 的层级侧边栏 | 是(左侧桌面端,移动端抽屉) |
Main | 渲染的页面内容(MDX 或 API 页面) | 可滚动 |
ContentActions | 复制页面内容、复制原始 Markdown / OpenAPI 等操作 | 是(内容顶部) |
虚拟模块系统
渲染器依赖构建时由插件生成的虚拟模块。它们是插件和渲染器之间唯一的桥梁。
virtual:clarify-routes
路由清单和导航树。
export const routes: Array<{
path: string
title: string
component: React.ComponentType
kind: 'mdx' | 'openapi'
sections?: Array<{ id: string; title: string }>
contentArtifactUrl?: string
}>
export const navigation: NavigationTree
virtual:clarify-openapi-registry
解析后的 OpenAPI 规范注册表。
export const openApis: Record<string, OpenAPISpec>
virtual:clarify-config
解析后的项目配置和构建选项。
export const config: ResolvedProjectConfig & ResolvedBuildOptions
virtual:clarify-page/*
每个 MDX / Markdown 页面是一个独立的虚拟模块:
// virtual:clarify-page/getting-started
export { default } from '/absolute/path/to/source/getting-started.mdx'
Renderer 消费的路由数据
Renderer 不关心文件系统,只消费 CLI 生成的 route item:
| 字段 | 用途 |
|---|---|
path | React Router 匹配和导航 active 状态。 |
title | 页面标题和导航显示。 |
description / keywords | Hydration 后的运行时 metadata 更新。 |
kind | 区分 MDX 和 OpenAPI 页面。 |
locale、basePath、alternates、isFallback | 语言上下文和语言切换。 |
sections | 侧边栏锚点和页面内导航。 |
contentArtifactUrl | raw Markdown/OpenAPI 内容复制和链接操作。 |
component / lazy | 页面组件导入策略。 |
如果需要新增渲染器消费的 route 字段,应先确认 CLI 能同时为客户端和服务端虚拟模块稳定生成该字段。
加载、404 与渲染错误状态
PageSkeleton 是懒加载页面模块的 Suspense fallback。它会模拟真实文档结构,包括标题、摘要、卡片、正文、代码块和参考网格,让路由切换时保持稳定,而不是展示空白占位。
客户端未知路径由 AppShell 的 catch-all 路由处理:
- 如果当前语言存在
/404内容页,Clarify 会复用该 MDX 页面作为未找到页面; - 如果没有自定义
/404,则显示内置的本地化 404 面板; - 因为未知路径没有 active content route,内容操作和上一页/下一页导航会保持隐藏。
页面组件外层还包裹了 PageErrorBoundary。当页面在客户端渲染时抛错,Clarify 会展示本地化错误面板,包含当前路径、刷新按钮和可展开的错误详情。构建期 SSR 错误仍遵循 SSG 的 failOnError 策略。
主题与界面语义层
Renderer 的视觉系统分为两层:
- 主题 token 层:由项目配置和内置主题解析得到,表达品牌色、表面色、边框、圆角和布局宽度等基础设计值。
- 界面语义层:由渲染器根据主题 token 派生,表达菜单、导航、搜索、按钮状态、说明文字等组件角色。
组件编写遵循以下原则:
- 基础属性可以直接使用主题 token:背景、边框、圆角、品牌色、内容前景色等稳定基础值,可以直接消费主题 token。
- 组件角色应使用界面语义层:菜单项、导航链接、搜索结果、说明文字、hover / active 状态、菜单宽度等跨组件复用规则,应通过语义类或语义变量表达。
- 避免在组件内重复设计规则:不要在多个组件里分散定义颜色混合、透明度、字号、行高或宽度;这些规则应收敛到渲染器样式层统一维护。
- 优先使用自适应布局:使用流式 grid、
minmax(0, 1fr)、w-full、h-full和明确的 overflow 边界,不在 JSX 中写固定预览宽高或一次性尺寸。 - 不要直接使用 Tailwind 任意值:字号、行高、mask、grid track、宽高等如果需要超出默认 scale,应移入渲染器 CSS class,让组件结构保持清晰。
- 不要过度变量化:只有公开主题能力、用户可配置项,或跨多个组件复用的语义规则才需要提升为 CSS 变量;没有复用计划、也不准备开放配置的私有实现值,应保留为内部 class 的普通 CSS 声明。
- 预览也必须遵守体系:preview / marketing 相关的 renderer 入口可以裁切溢出内容,但私有测量值应留在 renderer CSS 中,而不是散落在组件 JSX 中。
- 最小可读区域通过布局表达:组件需要可读宽度时,应通过响应式布局、内部 class 和裁切行为表达,而不是固定
w-*快照。 - 保持主题系统单一来源:公开主题只负责基础设计能力;内部语义层负责把基础 token 翻译成具体 UI 角色,确保 Header、Navigation、Search、移动端抽屉和 Portal 菜单在不同主题下保持一致。
- 谨慎扩展公开主题能力:如果未来需要开放更细粒度的视觉控制,应先确认它是否属于用户可配置的主题能力,再提升到主题 token 层;否则继续作为渲染器内部语义规则维护。