VitePress 文档编写指南
本文档说明如何在 docs/dev/ 下新建或维护开发文档,重点介绍 order 排序策略。
1. order 排序策略
order 值决定文档在侧边栏中的显示顺序(数值越小越靠前)。本项目采用分段策略,按重要程度和入门阶段划分区间:
| 区间 | 定位 | 典型内容 |
|---|---|---|
0 – 99 | 入门阶段:新开发者必读,按阅读顺序排列 | 环境搭建、架构概览、通信机制、任务模型、构建命令 |
100 – 199 | 重要参考:日常开发频繁查阅的规范与指南 | 错误处理、JSON 处理、测试规范、Commit 风格、本文档 |
200 – 299 | 专题深入:特定功能或机制的详细说明 | 图片代理、缓存机制、特定模块设计 |
300 – 399 | 进阶/扩展:不常用但有时需要参考的内容 | 性能优化、部署配置、平台差异 |
500 – 599 | 归档/参考:历史设计文档、已完成的方案讨论 | plans/ 下的架构设计文档 |
plans/子目录有自己的 order 命名空间,其内部 order 值相互独立,不与docs/dev/冲突。
当前文档 order 分配
入门阶段(0–99)
| order | 文件 | 标题 |
|---|---|---|
| 10 | setup.md | 开发环境搭建 |
| 20 | architecture.md | 项目架构 |
| 30 | frontend-backend-bridge.md | 前后端通信通道 |
| 40 | task-model.md | 任务模型 |
| 50 | build.md | 构建指南 |
重要参考(100–199)
| order | 文件 | 标题 |
|---|---|---|
| 110 | error-handling.md | 错误处理指南 |
| 120 | json-handling.md | 优雅的 JSON 处理指南 |
| 130 | testing.md | 测试指南 |
| 140 | git-commit-style.md | Git Commit 风格指南 |
| 199 | vitepress-guide.md | VitePress 文档编写指南(本文) |
专题深入(200–299)
| order | 文件 | 标题 |
|---|---|---|
| 210 | image-proxy.md | 图片代理与缓存 |
2. 新建文档步骤
- 在
docs/dev/下创建.md文件 - 添加 frontmatter,至少包含
title和order:
md
---
title: 你的文档标题
order: 150
---- 根据文档定位选择合适的 order 区间(参考上表)
- 同一区间内按重要程度间隔 10 分配(如 110、120、130),留出插入空间
3. 文档结构约定
- 用
---水平线分隔主要章节 - 代码示例用
::: code-group展示多语言对比 - 引用项目内其他文档用相对路径:
[错误处理指南](./error-handling) - 引用源码用注释标注文件路径:
// shared/src/commonMain/.../xxx.kt
4. 引用源码文件
VitePress 支持用 <!--@include: path--> 直接嵌入源码文件内容,适合需要与源码保持同步的文档:
md
::: code-group
\`\`\`ts [bridge.ts]
// fredica-webui/app/util/bridge.ts
/**
* kmpJsBridge 安全封装。
*
* 问题根源:WebView 注入 window.kmpJsBridge 对象后,
* 其内部 postMessage 通道需要额外数百毫秒才就绪。
* 在此期间调用 callNative 会同步抛出
* "window.kmpJsBridge.postMessage is not a function"。
*
* 解决方案:callBridge() 内置启动期重试,对所有调用方透明,
* 调用方无需自行添加 try-catch 或重试逻辑。
*/
/** bridge 对象不存在(非 WebView 环境,如普通浏览器)时抛出此错误。 */
export class BridgeUnavailableError extends Error {
constructor() {
super("kmpJsBridge 不可用(非 WebView 环境)");
this.name = "BridgeUnavailableError";
}
}
/**
* 安全调用 kmpJsBridge.callNative,返回 Promise<string>。
*
* - bridge 不可用(浏览器环境)→ 抛出 BridgeUnavailableError
* - callNative 同步抛出(postMessage 通道未就绪)→ 自动重试,最多 maxRetries 次
* - 超过最大重试次数 → 抛出最后一次错误
*
* @param method bridge 方法名
* @param params JSON 字符串参数,默认 "{}"
* @param options maxRetries(默认 5)/ retryDelayMs(默认 300ms)
*/
export async function callBridge(
method: string,
params: string = "{}",
{ maxRetries = 5, retryDelayMs = 300 }: { maxRetries?: number; retryDelayMs?: number } = {},
): Promise<string> {
const bridge = typeof window !== "undefined" ? window.kmpJsBridge : undefined;
if (!bridge) {
console.debug(`[callBridge] bridge unavailable (non-WebView env), method=${method}`);
throw new BridgeUnavailableError();
}
let lastError: unknown;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
if (attempt > 0) {
await new Promise<void>(r => setTimeout(r, retryDelayMs));
}
try {
return await new Promise<string>((resolve, reject) => {
try {
bridge.callNative(method, params, resolve);
} catch (e) {
reject(e); // 同步抛出(bridge 未就绪)→ 触发重试
}
});
} catch (e) {
lastError = e;
}
}
throw lastError;
}
/**
* callBridge 的静默变体:bridge 不可用时返回 null,其他错误正常抛出。
*
* 适用于浏览器开发环境下可静默跳过的调用(如 useEffect 内的数据加载)。
* 调用方只需 `if (!raw) return` 即可,无需 catch BridgeUnavailableError。
*/
export async function callBridgeOrNull(
method: string,
params: string = "{}",
options?: { maxRetries?: number; retryDelayMs?: number },
): Promise<string | null> {
try {
return await callBridge(method, params, options ?? {});
} catch (e) {
if (e instanceof BridgeUnavailableError) return null;
throw e;
}
}
/**
* 打开内部路由(应用内页面)。
* - WebView 环境:调用 open_internal_tab bridge,在系统浏览器(Phase 1)或新 WebView 窗口(Phase 6)打开。
* - 普通浏览器开发环境:window.open 新标签。
*/
export function openInternalUrl(path: string) {
if (typeof window === "undefined") return;
if (window.kmpJsBridge) {
callBridge("open_internal_tab", JSON.stringify({ path })).catch(() => {});
} else {
window.open(path, "_blank", "noopener,noreferrer");
}
}
/**
* 打开外部 URL。
* - WebView 环境(kmpJsBridge 存在):调用 open_browser bridge,由原生系统浏览器打开。
* - 普通浏览器环境:直接 window.open。
*/
export function openExternalUrl(url: string) {
if (typeof window === "undefined") return;
if (window.kmpJsBridge) {
callBridge("open_browser", JSON.stringify({ url, addServerInfoParam: false })).catch(() => {});
} else {
window.open(url, "_blank", "noopener,noreferrer");
}
}
\`\`\`
:::路径相对于当前 .md 文件位置。