使用 fumadocs-obsidian 将 Obsidian 库渲染为 Fumadocs 文档站点

发布时间:2026/9/15 12:41:06
使用 fumadocs-obsidian 将 Obsidian 库渲染为 Fumadocs 文档站点 使用 fumadocs-obsidian 将 Obsidian 库渲染为 Fumadocs 文档站点【免费下载链接】fumadocsThe beautiful flexible React.js docs framework.项目地址: https://gitcode.com/GitHub_Trending/fu/fumadocsObsidian 库vault是很多技术团队沉淀知识、撰写笔记的场所但 Obsidian 本身并不直接提供面向读者的文档站点。本文介绍当前仓库中 packages/obsidian 提供的fumadocs-obsidian集成包如何将一个 Obsidian 库作为运行时内容源接入 Fumadocs如何通过内置的 remark 插件把 Wikilink、嵌入、Callout、块 ID、注释等 Obsidian 专属语法转换为标准 Markdown/MDX以及静态生成与动态重新验证两种接入方式的差异。读完本文你将能够把现有笔记库零迁移地变成一个可搜索、可复用 Fumadocs 全部主题能力的文档站。一、fumadocs-obsidian 是什么fumadocs-obsidian是 Fumadocs 生态中的 Obsidian 集成包定位为Runtime content source运行时内容源它不是把笔记预先转换为文件再参与构建而是在运行时直接读取 Obsidian 库通过 Fumadocs 的内容源content source抽象将库中的 Markdown 笔记渲染成页面。它的官方能力清单见 packages/obsidian/README.md包括Wikilinks[[链接]]与嵌入![[嵌入]]Callouts [!note]等Block IDs块 ID注释%% ... %%支持静态static与动态重新验证dynamically revalidated两类 Fumadocs 内容源。从 packages/obsidian/CHANGELOG.md 可以梳理出它的演进脉络这也解释了它当前的设计取向v1.0.0正式发布 Obsidian content source v1通过静态或动态 Fumadocs 源直接渲染 Obsidian 库采用惰性内存编译lazy in-memory compilation与本地内容热重载同时移除了旧的生成文件与 remark 插件集成方案v1.0.3搜索索引改为从page.data.structuredData()读取结构化数据与load()共享编译结果避免重复编译v1.0.4内部将cnfast替换为cn纯内部重构对外 API 无变化。因此无论你看到的是老教程中的把 vault 转成文件再构建还是本仓库中的运行时源方案本文描述的都是 v1 的新架构。二、快速接入一个可运行的示例仓库在 examples/obsidian 中提供了一个完整可运行的示例其内容源定义在 examples/obsidian/lib/source.tsimport { dynamicLoader } from fumadocs-core/source; import { obsidian } from fumadocs-obsidian; const vault obsidian({ dir: public/vault, url: (path) /vault/${path}, }); if (process.env.NODE_ENV development) { void vault.devServer(); } const vaultLoader dynamicLoader(vault.dynamicSource(), { baseUrl: /docs, }); export function getSource() { return vaultLoader.get(); }这段代码演示了接入的最核心三件事用obsidian()创建内容源传入dir指向 Obsidian 库根目录url回调为库中的媒体文件图片等生成公开访问 URL开发环境启动 devServer通过vault.devServer()连接本地内容热重载服务在 Vite 下更推荐使用fumadocs-obsidian/dev/vite导出的watchWithVite()见 packages/obsidian/src/dev/vite.ts这样编辑笔记时站点会即时刷新接入 Fumadocs 的 loadervault.dynamicSource()拿到动态源后交给dynamicLoader生成带baseUrl的路由 loader最终通过getSource()在页面中使用。示例项目还包含一个可直接查看的演示 vault位于 examples/obsidian/public里面有用到的.md笔记与图片资源可以对照源码验证 Wikilink、Callout 等语法的实际渲染效果。三、obsidian()配置项详解obsidian()的完整配置类型定义在 packages/obsidian/src/source.ts 的ObsidianConfig中export interface ObsidianConfig FrontmatterSchema extends StandardSchemaV1, MetaSchema extends StandardSchemaV1, extends ObsidianCompilerOptions, PickVaultStorageOptions, url { /** Obsidian 库的根目录 */ dir: string; /** 扫描的 glob 模式相对 vault 目录 */ include?: string[]; frontmatterSchema?: FrontmatterSchema; metaSchema?: MetaSchema; }各字段的作用与默认值如下配置项类型默认值说明dirstring必填Obsidian 库根目录所有文件解析均相对该目录进行includestring[][**/*]defaultInclude用 tinyglobby 扫描文件的 glob 模式可通过它排除部分目录url(vaultPath, mediaFile) string \| undefined默认/${path}根相对路径为媒体文件生成公开 URL返回undefined表示该文件仅可通过相对路径访问frontmatterSchemaStandardSchemaV1内置的frontmatterSchema校验页面 frontmatter 的 schemametaSchemaStandardSchemaV1fumadocs-core/source/schema的metaSchema校验 meta 数据文件JSON的 schema需要说明的是包内置的默认 frontmatter schema 定义在 packages/obsidian/src/utils/schema.ts它刻意保持宽容export const frontmatterSchema z .object({ title: looseString, // 标量数字/布尔也会被转为字符串如 title: 2024 description: z.string().optional(), icon: z.string().optional(), full: z.boolean().optional(), aliases: z.union([z.string().transform((alias) [alias]), z.array(z.string())]).optional(), _openapi: z.record(z.string(), z.unknown()).optional(), }) .loose();由于 Obsidian 笔记往往结构松散looseString会把title: 2024这类标量值自动转成字符串而不是拒绝整篇页面schema 使用.loose()未知字段不会导致校验失败。aliases同时支持单个字符串或字符串数组。如果你的笔记有自定义 frontmatter 字段可以传入自定义 schema支持 Standard Schema 规范的任意实现。在 packages/obsidian/src/source.ts 的parse()实现中可以看到校验失败时的行为frontmatter 不合法会抛出带文件绝对路径与具体 issue 的错误invalid frontmatter in ...而 meta 数据文件校验失败则提示invalid data in ...。值得注意的是vault 存储层不做校验——RawFrontmatter在 build-storage.ts 中被保留为未校验状态单个格式错误的笔记不会拖垮整个 vault只有真正解析该页面时才会暴露问题。四、Obsidian 语法如何被编译为标准 Markdownfumadocs-obsidian的核心是一个 unified 编译管线。createProcessor见 packages/obsidian/src/source.ts按顺序组装了以下插件链remarkParse → remarkGfm → Obsidian 专属 remark 插件解析 vault 语法 → remarkHeading / remarkImage / 自定义 remarkPlugins / remarkStructure → remarkRehypepassThrough: mdxJsxFlowElement / mdxJsxTextElement → rehypeCode / 自定义 rehypePlugins / rehypeToc其中 Obsidian 专属插件由 packages/obsidian/src/remark/index.ts 的getRemarkPlugins()统一导出顺序固定remarkWikilinks先解析 Wikilink把[[...]]语法转换成标准链接或图片节点这样后续插件拿到的是规范 MarkdownremarkConvert将 Callout 块引用和相对链接/图片 URL 转换/重写remarkObsidianComment移除%% ... %%注释remarkBlockId把块 ID 包装为带id属性的 section。4.1 Wikilinks 与嵌入remarkWikilinks实现见 packages/obsidian/src/remark/remark-wikilinks.ts用正则匹配!?\[\[...]]并支持三种语法变体[[note]] → 链接到 note.md [[note#标题]] → 链接到 note.md 的某个标题 [[note|别名]] → 使用自定义显示文本 ![[image.png]] → 嵌入图片 ![[note]] → 嵌入笔记有限支持对于普通链接插件会解析出目标文件的相对路径并生成带data.isWikiLink标记的链接节点同时保留#heading锚点锚点由 packages/obsidian/src/utils/get-refs.ts 的getHeadingHash用github-slugger生成与 Fumadocs 的标题 slug 规则保持一致而以^开头的块 ID 引用则跳过 slugify直接使用块 ID 本身。嵌入场景下图片会被转换为img节点URL 取自媒体文件的url嵌入内容块则输出为include的 MDX JSX 节点。需要注意源码中给出的两条限制见 remark-wikilinks.ts 的console.warn嵌入内容块embed content block的部分功能尚未支持以及不支持![[image.png|300]]这种指定图片尺寸的写法。4.2 CalloutsremarkConvert见 packages/obsidian/src/remark/remark-convert.ts会把 Obsidian 的 [!type]引用块转换成 Fumadocs 的 callout 组件。语法格式为 [!note] 标题 正文内容也支持可折叠 Callout [!note]RegexCalloutHead匹配^\!(?type\w)?。同一个插件还会重写普通的相对链接与图片 URL先decodeURI还原 URL 编码的相对路径再通过 vault 解析器解析到目标文件锚点链接则统一 slugify——这正是 CHANGELOG v1.0.0 中提到的Resolve URL-encoded relative file links against their decoded source paths按解码后的源路径解析 URL 编码的相对文件链接。4.3 块 IDremarkBlockId见 packages/obsidian/src/remark/remark-block-id.ts识别段落末尾的^blockid语法把它包装成section id^blockid从而支持从其他笔记[[note#^blockid]]精确跳转。块 ID 的匹配规则是行尾的^后跟单词字符且反斜杠转义的\^不会被识别。4.4 注释remarkObsidianComment见 packages/obsidian/src/remark/remark-obsidian-comment.ts递归匹配%% ... %%分隔符并删除其中的内容(?!\\)%%保证转义后的\%%不会被当作注释起点。这让你可以像在 Obsidian 中一样在笔记里写临时批注发布时自动消失。4.5 编译管线中的其他内置插件除了 Obsidian 专属语法编译管线还默认注入了fumadocs-core的 MDX 插件均可用false关闭或传入配置对象覆盖相关配置类型见 packages/obsidian/src/source.ts 的ObsidianCompilerOptionsremarkGfmGFM 表格、删除线、任务列表等remarkHeading为标题生成 ID默认generateToc: falseTOC 交由 rehype 阶段收集remarkImage为 Next.js Image 注入图片尺寸注意这里强制useImport: false——因为从 AST 中无法渲染 import图片仅通过 URL 方式工作publicDir默认是./publicremarkStructure收集结构化数据供搜索索引使用rehypeCode代码高亮默认fallbackLanguage: plaintext——这个默认值很有用Obsidian 库中常出现 dataview、tasks 等插件专属的代码块语法将其降级为纯文本渲染避免整页构建失败rehypeToc收集目录exportToc以data形式输出。五、页面模型load()、structuredData()与渲染器5.1 页面数据结构obsidian()返回的内容源中每个页面都符合ObsidianPage结构见 packages/obsidian/src/source.tsexport interface ObsidianPageFrontmatter Recordstring, unknown extends PageData { title: string; description?: string; icon?: string; content: string; // 去除 frontmatter 后的 Markdown 原文 frontmatter: Frontmatter; // 经 schema 校验后的 frontmatter load: () PromiseObsidianRenderer; structuredData: () PromiseStructuredData; }这里有两个关键设计惰性编译lazy compilationload()内部通过loaded ?? compilePage(...)缓存编译结果同一份页面在内存中最多编译一次结构化数据共享编译CHANGELOG v1.0.3 说明structuredData()不再回退到(await page.data.load()).structuredData而是直接暴露在 page data 上与load()共享同一编译产物const structuredData await page.data.structuredData();load()返回的渲染器仍然带有structuredData字段因此旧代码无需改动即可继续工作。5.2 渲染器不执行任意 JavaScript编译后的页面由 packages/obsidian/src/renderer.ts 的createRenderer负责渲染。ObsidianRenderer提供三个方法render(components?)异步渲染返回{ toc, body }renderSync(components?)同步渲染serialize()返回可序列化的编译产物可通过fumadocs-obsidian/client的rendererFromSerialized恢复。渲染过程使用hast-util-to-jsx-runtime把 hast 树映射为 React JSX。其安全模型值得强调vault 内容是纯 Markdown渲染只做 AST → JSX 的映射绝不求值内容中的任意 JavaScript。渲染器内部传入的evaluater只允许解析标识符用于把 JSX 组件名映射到传入的components任何表达式求值evaluateExpression或程序求值evaluateProgram都会直接抛错——这从机制上防止了笔记内容变成可执行代码静态生成时可以放心使用。六、vault 的文件扫描、内存缓存与热重载6.1 文件分类obsidian()用 tinyglobby 按include模式扫描 vault 目录源码见 packages/obsidian/src/source.ts 的createVault路径统一slash()化并按字母排序保证名称解析的确定性。每个文件按扩展名被划分为三类见 packages/obsidian/src/build-storage.ts 的getFileFormat类型扩展名处理方式content.md/.mdx读取内容解析 frontmatterdata.json/.yaml/.yml/.toml作为 meta/数据文件处理JSON 会被metaSchema校验media其他所有只记录路径与 URL内容永不读入内存content 文件使用fumadocs-core/content/md/frontmatter解析 YAML frontmatter保留原始未校验数据media 文件的 URL 由url配置生成默认是根相对路径/${path}。此外normalize会对越出 vault 目录的../路径直接抛错points outside of vault folder。6.2 增量缓存与失效策略源码中维护了三层缓存状态vaultFiles跨快照snapshot持久保存的 vault 文件 Map失效时只重读发生变化的文件invalidated等待下次快照重读的绝对路径集合flush全量清空标记。invalidateFile(file)的做法尤其值得注意因为每一页都可能从 vault 中的任何其他文件解析名称与别名单文件变更后为了不让重命名留下失效链接会重建整个快照但只从磁盘重读被失效的那一个文件invalidateAll()则把清空操作推迟到下一次快照构建时执行避免与正在进行的构建产生写冲突。快照构建通过buildQueue串行化构建失败时只丢弃失败的那次快照vault undefined不会让瞬时错误污染后续构建。文件读取采用 100 个一组的并发分块ReadChunkSize 100并容忍扫描后、读取前被删除的竞态——读失败时只记录错误并跳过该文件而不是让整个 vault 失败。6.3 开发体验本地热重载开发模式下可以通过两种方式启用热重载独立 dev server调用vault.devServer(url?)连接独立的 local-content dev serverfumadocs-obsidian还提供了 CLI 入口packages/obsidian/package.json 的bin字段为fumadocs-obsidian实现在 packages/obsidian/src/bin.tsVite 集成从fumadocs-obsidian/dev/vite导入watchWithVite()或localContentPlugin见 packages/obsidian/src/dev/vite.ts在 Vite/Next.js 开发服务器内直接监听 vault 文件变化。七、静态生成与动态重新验证两种模式fumadocs-obsidian底层构建在fumadocs/local-content见 packages/obsidian/package.json 的依赖之上obsidian()返回的ObsidianSource本质上是一个LocalSource。因此它与 Fumadocs 的静态/动态源机制完全兼容静态模式构建期调用vault.staticSource()等方法把所有页面编译结果一次性产出适合内容基本不变的笔记库动态模式如示例所示用vault.dynamicSource()dynamicLoader组合服务端在请求时按需读取、缓存和失效配合 CHANGELOG v1.0.0 提到的 dynamically revalidated 能力可以做到笔记更新后站点内容自动更新。两种模式共用同一套ContentIntegrationparse回调见 packages/obsidian/src/source.ts差异只在于页面数据的读取时机与缓存策略因此切换成本很低。八、已知限制与注意事项综合 CHANGELOG、README 与源码注释使用时有几点需要提前了解嵌入内容块的部分支持![[other note]]形式的笔记嵌入输出为includeJSX 节点但源码明确警告其部分功能尚未支持建议以[[链接]]形式为主不支持嵌入图片尺寸语法![[image.png|300]]中的|300会被忽略源码仅console.warn如需控制尺寸请用标准 Markdown/HTML图片以 URL 方式工作remarkImage强制useImport: false图片必须能通过url配置映射为可访问的公开路径dataview 等插件代码块自动降级rehypeCode的fallbackLanguage: plaintext让插件专属代码块以纯文本展示不会导致构建失败frontmatter 校验是逐页、惰性的格式错误的笔记只在它被编译时暴露错误不影响其他页面但 schema 校验失败会抛出带路径的错误信息需注意排查环境要求fumadocs-obsidian的 peerDependencies 要求fumadocs-core^16.8.0、React 19并依赖fumadocs/local-content同仓库 packages/local-content接入前请确认版本匹配。九、总结fumadocs-obsidian提供了一条从 Obsidian 笔记到专业文档站点的低成本路径obsidian()把整个 vault 变成 Fumadocs 运行时内容源内置的 remark 插件链在编译期把 Wikilink、嵌入、Callout、块 ID、注释等 Obsidian 语法转换为标准 Markdown/MDX渲染器则保证内容只被映射为 React 组件而绝不求值任意代码。通过惰性编译与增量快照缓存静态生成和动态重新验证两种模式都能获得合理的构建性能与开发期热重载体验。对于已经深度使用 Obsidian 双链笔记、Callout 与块引用写作的团队可以在不迁移、不转换文件的前提下把同一套笔记同时用作内部知识库与对外文档站。更详细的 API 与配置可以继续阅读 packages/obsidian/src 下的源码与 examples/obsidian 示例项目。【免费下载链接】fumadocsThe beautiful flexible React.js docs framework.项目地址: https://gitcode.com/GitHub_Trending/fu/fumadocs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考