Skip to content

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文件标题
10setup.md开发环境搭建
20architecture.md项目架构
30frontend-backend-bridge.md前后端通信通道
40task-model.md任务模型
50build.md构建指南

重要参考(100–199)

order文件标题
110error-handling.md错误处理指南
120json-handling.md优雅的 JSON 处理指南
130testing.md测试指南
140git-commit-style.mdGit Commit 风格指南
199vitepress-guide.mdVitePress 文档编写指南(本文)

专题深入(200–299)

order文件标题
210image-proxy.md图片代理与缓存

2. 新建文档步骤

  1. docs/dev/ 下创建 .md 文件
  2. 添加 frontmatter,至少包含 titleorder
md
---
title: 你的文档标题
order: 150
---
  1. 根据文档定位选择合适的 order 区间(参考上表)
  2. 同一区间内按重要程度间隔 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 文件位置。

Fredica — AI 视频工坊