夜雨聆风学习资料网

ARTICLE · 981287

【工程篇】单向文档投影:如何让 VitePress 在线文档站与 GitHub 源码 100% 保持同步

【工程篇】单向文档投影:如何让 VitePress 在线文档站与 GitHub 源码 100% 保持同步

“文档站不是第二份代码库,而是原生 Markdown 源码的一面镜子。把静态站当成编译产物的单向投影,才能从根源上终结两套文档脱节的宿命。”


一、现实痛点:文档站与代码库的“双头脱节”

在维护现代开源项目或企业级框架时,工程团队往往面临着一个经典的体验冲突:

  • 在 GitHub 侧
    :希望开发者在仓库中浏览 docs/*.md 时,拥有最原生、最纯粹的 Markdown 阅读体验,所有相对链接(如 ../api/session.md)与图片均能无缝点击跳转;
  • 在公网在线站侧
    :又希望搭建一套基于 VitePress / Docusaurus 的华丽静态文档站,具备搜索、多语言切换、侧边栏导航与优雅的排版。

为了兼顾两者,许多团队最常见的做法是在仓库里建一个 website/ 目录,然后把 docs/ 里的 Markdown 文件人肉复制一份过去。

这种“双头维护”模式必然会带来三大灾难:

  1. 事实源分裂与文档漂移(Doc Drift)
    开发者修了一个 Bug,顺手更新了 docs/core.md,但忘了同步更新 website/src/core.md。一个月后,公网文档站与 GitHub 源码文档彻底分道扬镳,用户在群里不断吐槽“照着官网文档写代码跑不通”。
  2. 相对路径打包后大面积 404
    在 GitHub 仓库中有效的相对文件链接(例如跳到 packages/core/src/index.ts),在经过 VitePress 静态打包后直接变成断链或 404 错误页面;跨页面的 Heading 锚点也因生成器 Slug 算法不同而频繁失效。
  3. 临时构建缓存污染代码库
    为了让 VitePress 识别文档,开发者在 website/ 目录下生成了大量中间文件,稍有不慎就通过 git add 提交进了版本库,导致 Git 历史充斥着机器生成的脏文件。

DeepSeek Harness(DSH)提出的 dsh-doc-site-sync 技能,以一种极其优雅的架构范式终结了这种混乱:“源码是唯一真相,静态站只是单向编译投影(Single Source of Truth & Unidirectional Projection)”。


二、单向文档投影架构设计

DSH 严格规定:全仓所有的技术文档只能在 docs/ 原生目录下编写一次。

在线静态文档站绝不拥有独立的 Markdown 源文件,而是在构建时通过自动化脚本将 docs/ 投影为瞬时的临时目录树:

 ┌─────────────────────────────────────────────────────────────┐
 │                  唯一事实源 (Source of Truth)                │
 │  • 原生 Markdown: docs/**/*.md (支持 GitHub 原生相对跳转)     │
 │  • 声明式清单:   website/docs.ts (严格控制对外公开发布边界)  │
 └──────────────────────────────┬──────────────────────────────┘
                                │
                                ▼
 ┌─────────────────────────────────────────────────────────────┐
 │            AST 单向投影引擎 (project-doc-site.ts)           │
 │  • 路径映射与双语派生: foo.zh.md -> / , foo.md -> /en/      │
 │  • 链接智能重写: 站内转 Route,源码转 GitHub SHA 带行号      │
 │  • 静态资源搬运: 图片自动提取至临时静态目录                  │
 └──────────────────────────────┬──────────────────────────────┘
                                │
                                ▼
 ┌─────────────────────────────────────────────────────────────┐
 │         临时生成树 (website/.generated/ - 瞬时用后即弃)      │
 └──────────────────────────────┬──────────────────────────────┘
                                │
                                ▼
 ┌─────────────────────────────────────────────────────────────┐
 │          VitePress 编译器  ──>  最终公网静态站点 HTML       │
 └─────────────────────────────────────────────────────────────┘

三、四大核心机制深度拆解

1. 源码即唯一真相:严禁手改生成目录

在 DSH 的开发守则中,确立了不可逾越的铁律:

  • 绝对禁止
    在 website/.generated/、website/.cache/ 或 website/zh-CN/ 等目录下直接新建或修改 Markdown 文件;
  • 所有的修改、翻译和排版,必须且只能发生在 docs/ 原生目录下的源文件中;
  • website/.generated/
     被列入 .gitignore,它在本地构建与 CI 中完全是瞬时、可随时清空、用后即弃的派生物。

2. 声明式映射清单(website/docs.ts)

哪些文档该对外发布?公网侧边栏如何组织?DSH 通过 website/docs.ts 进行全声明式配置,使用 pairedPages() 契约自动管理:

import { pairedPages, typeDocsPage } from'./docs';

exportconstpages: DocsPage[] = [
pairedPages({
source: 'docs/architecture.md',      // 原生英文 Markdown 路径
route: 'architecture.md',           // VitePress 路由路径
sidebar: 'zh-guide',                // 所属侧边栏集合
section: '核心架构解析',             // 分组标题
order: 1,                           // 排序权重
  }),
];

🌟 声明式清单的两大精妙设计:

  1. 自动派生中英双语路由
    :只需声明英文源文件路径,pairedPages() 会自动识别同级的 docs/architecture.zh.md,并将中文自动投射到根路由(/architecture),英文自动投射到 /en/architecture;
  2. 构建公开发布白名单
    :开发团队内部沉淀的临时 RFC、事故复盘(Postmortem)、内部测试用例文档等虽然存放在 docs/ 下,但只要未在 docs.ts 中注册,投影引擎就会自动将其隔离,绝不会泄露到公网文档站。

3. 智能链接与资源 AST 重写(Link Projector)

当原生 Markdown 被投影到 VitePress 目录树时,投影引擎(project-doc-site.ts)会遍历 Markdown AST,执行四级智能重写规则:

                    【AST 链接投影四级重写规则】
 ┌───────────────────────┬─────────────────────────────────────────────┐
 │ 目标类型              │ 投影转换结果                                │
 ├───────────────────────┼─────────────────────────────────────────────┤
 │ 目标在 docs.ts 清单内 │ 自动重写为静态站相对路由 (如 /architecture) │
 ├───────────────────────┼─────────────────────────────────────────────┤
 │ 目标为仓内源码/单测   │ 自动转为 GitHub 官方源码链接 (带精确行号)   │
 ├───────────────────────┼─────────────────────────────────────────────┤
 │ 相对图片路径          │ 自动复制图片至静态目录并重写引用 URL        │
 ├───────────────────────┼─────────────────────────────────────────────┤
 │ 目标文件不存在        │ 🚨 投影构建直接阻断报错,杜绝死链上线       │
 └───────────────────────┴─────────────────────────────────────────────┘

💻 举个生动的转换例子:

在 docs/guide.md 中写下这样一行原生 Markdown:

参考 [会话管理实现](../packages/core/src/session.ts#L15-L30) 与 [架构文档](./architecture.md)。

经过单向投影引擎编译后,在 VitePress 静态站中会自动被智能转译为:

参考 <ahref="https://github.com/my-org/my-repo/blob/master/packages/core/src/session.ts#L15-L30">会话管理实现</a> 与 <ahref="/architecture">架构文档</a>。

无论读者是在 GitHub 网页浏览,还是在独立文档站查阅,所有链接 100% 顺畅直达!


4. 跨语言锚点一致性检查(verify-doc-site-fragments)

VitePress 在根据标题生成 URL Slug 锚点时,对中文字符的处理算法与 GitHub 默认的 Slug 生成器存在细微差异。

为了防止多语言跳转时出现“明明页面存在,但跳不到指定小节”的尴尬断层,DSH 制定了双重保障机制:

  1. 显式插入 HTML 锚点
    :在需要跨语言、跨页面引用的关键标题上方,显式标注固定 ID:
    <aid="disposer-rule"></a>
    ### 资源注销闭环(Disposer Rule)
  2. CI 自动化锚点门禁
    :在提交前运行 pnpm docs:check,静态扫描全站所有页面内部跳转锚点,一旦发现悬空 Fragment 立即阻断。

🛠️ DSH 文档站维护核心命令速查表

命令
执行时机
核心作用
典型错误后果
pnpm docs:dev
本地编写与排版文档时
启动 VitePress 实时热更新预览服务
❌ 手动修改 .generated/ 内容,下次重启直接被覆盖丢失
pnpm docs:check
修改文档结构或新增页面后
校验映射清单、路由完整性与所有页面锚点
❌ 未跑检查直接上线,导致公网站点出现 404 页面或死锚点
pnpm run doc-sync
Git Commit 提交前的本地全量门禁
整合执行全仓坏链检查、字数预算与双语哈希校验
❌ 绕过检查直接推送,导致 CI 远端拦截

四、工程启示与落地指南

  1. 坚决杜绝文档的多份物理拷贝
    任何需要人工同步两份内容的架构设计都是不可持续的。将文档站定位为“编译后的只读视图”,是保持文档与源码强一致的唯一正道。
  2. 把链接重写交给编译器,把精力留给内容创作
    作者编写文档时应当且只应当关注“当前仓库的相对目录关系”。利用 AST 编译器自动适配 GitHub 与静态站两种形态,能极大解放团队心智。
  3. 建立文档站的自动化冒烟门禁
    像对待业务代码一样对待文档站:将死链检查(Link Check)和锚点验证(Fragment Check)作为 CI 的必跑项,让文档 Bug 在合并前被扼杀在摇篮里。

💡 下期预告(全专栏收官大结局):
源文档只改了一句话,AI 却把整篇几千字全重新翻译了一遍,导致社区人工润色的成果被无情覆盖;更要命的是代码块里的注释在中英文档里经常不同步!
最后一篇我们将拆解 dsh-translate-docs,看 DeepSeek 如何用「差异简报驱动(Briefing-Driven)」与「代码块字节全等门禁」打造极速、精准的双语国际化流水线!

欢迎「关注」专栏并设为「星标」🌟,第一时间获取硬核工程深度解析!如果本文对你有启发,请点个「在看」与「赞」支持一下!

相关学习资料