Comark内容分发:一份Markdown同时生成博客、邮件与RSS
Comark内容分发:一份Markdown同时生成博客、邮件与RSS
Comark 是一款开源的 Markdown 解析与渲染器,支持组件、属性和插件,可将同一份内容流式输出到 React、Vue、Svelte、Angular、HTML 和终端。它的核心能力是“一次解析、处处渲染”,让一份 Markdown 源稿同时驱动博客、邮件和 RSS 三类渠道,省去重复排版和维护多套模板的成本。
为什么内容分发总是很麻烦 🤯
传统做法里,同一条内容要发三个地方,就得维护三套东西:
- 博客:一套 Markdown 模板 + 框架渲染组件
- 邮件:一段内联样式的 HTML 字符串
- RSS:一段转义后、包着 CDATA 的 XML
每套都有自己的坑:邮件客户端不支持复杂 CSS、RSS 里一个 ]]> 就能毁掉整个 feed、博客里相对链接在别处打不开。内容一改,三处全要同步,新手很容易在这里踩坑。
Comark 的思路:解析一次,到处渲染 🧩
Comark 的解法是把“解析”和“渲染”彻底分开。
parseMarkdown() 把 Markdown 解析成一个可序列化的 JSON 文档(MarkdownDocument),包含 frontmatter(头部数据)、meta(插件元数据,如目录)和 nodes(正文节点)。这个文档不依赖任何框架,存进数据库、放进队列、随 API 传递都行。
有了这份“中间文档”,后面的事情就简单了:
| 渠道 | 渲染方式 | 说明 |
|---|---|---|
| 博客页面 | @comark/vue、@comark/react 等框架渲染器 |
浏览器里用组件渲染,支持自定义设计 |
| 邮件 | @comark/html 的 renderHtml() |
输出纯 HTML 字符串,组件用函数映射为内联样式 |
| RSS | 同上,再包一层 CDATA | 输出合法 XML 片段 |
文档模型详见 docs/content/1.getting-started/2.document-model.md,核心 API 源码在 packages/comark/src/parse.ts。
渠道一:博客——带 frontmatter 和目录的完整文章 📝
博客场景下,一个 Markdown 文件里可以同时写 YAML 头部、正文和自定义组件:
---
title: Hello World
pubDate: 2026-10-07
---
## Getting started
::alert{type="info"}
用你自己的设计系统渲染组件。
::
服务端或构建时调用一次 parseMarkdown(),就能拿到正文、标题、日期和目录,再交给框架渲染:
const tree = await parseMarkdown(markdown)
tree.frontmatter.title // 'Hello World'
tree.meta.toc.links // [{ id: 'getting-started', text: 'Getting started', depth: 2 }]
配合 shiki 插件做代码高亮、toc 插件自动生成侧边栏目录。Nuxt、Next.js、Astro、SvelteKit 都有完整示例,比如 examples/1.frameworks/nuxt/ 里的博客项目,详细指南见 docs/content/10.use-cases/7.docs-and-blogs.md。
渠道二:邮件——组件直接变成内联样式按钮 ✉️
邮件客户端不运行你的 Vue 或 React 组件,@comark/html 的解法是:把组件标签映射成返回 HTML 字符串的函数,样式直接内联:
const renderEmail = createHtmlRenderer({
components: {
button: async ([, attrs, ...children], { render }) =>
`<a href="${escapeHtml(String(attrs.href))}" style="display:inline-block;padding:12px 20px;background:#0f172a;color:#fff;border-radius:6px">${await render(children)}</a>`,
},
})
Markdown 里照常写组件语法,就能渲染出邮件按钮:
:button[Confirm your email]{href="https://example.com/confirm"}
还能通过 data 选项给每个收件人传不同的值(比如用户名),实现个性化问候。邮件与 RSS 的完整写法见 docs/content/10.use-cases/10.email-and-rss.md,HTML 渲染器实现位于 packages/comark-html/src/render.ts。
渠道三:RSS——一份合法 XML 的 feed 📡
RSS 的本质是把每条文章的标题和 HTML 包进 CDATA 段落。Comark 的做法是:先用 createHtmlRenderer() 把每篇 Markdown 渲染成 HTML,再写入 <item> 并处理两处易错点:
- CDATA 不能包含
]]>——需要把出现的]]>拆成]]]]><![CDATA[> - 链接 URL 要转义
&<>,保证 feed 是合法 XML
<item>
<title><![CDATA[文章标题]]></title>
<link>https://example.com/post/1</link>
<description><![CDATA[文章HTML正文]]></description>
</item>
另外提醒一个细节:邮件和 RSS 阅读器无法解析 /docs 这类相对链接,可以重写 a 标签把 href 补全为绝对地址;如果内容来自用户输入,建议再加上 security 插件做标签过滤。
三条渠道,一份源稿:内容还能继续扩展 🚀
除了博客、邮件、RSS,同一份 MarkdownDocument 还能渲染到更多地方:
- 网站前台:把 JSON 文档存库,任何客户端用
<MarkdownDocument>直接渲染,浏览器里无需解析器(参见 docs/content/10.use-cases/8.cms-runtime-content.md) - 终端:用
@comark/ansi输出带样式的 Markdown,适合 CLI 和 AI 编码助手,示例见 examples/3.cli/ansi/render.ts - AI 流式输出:auto-close 机制会自动补全未闭合的语法,AI 输出到一半也能稳定渲染
常见问题 💬
Q:邮件 HTML 能兼容所有邮件客户端吗? 不能。Comark 负责把 Markdown 和你的组件函数渲染成 HTML,最终效果取决于你写的标记和 CSS,复杂布局建议仍以内联样式为主。
Q:同一份 Markdown 能同时用在网站和邮件上吗?
可以。网站用框架渲染器,邮件用 @comark/html,为每个目标注册各自的组件即可,源稿完全共用。
Q:已经解析好的文档能直接渲染吗?
可以。renderHtmlFromDocument() 接收已存好的 MarkdownDocument,跳过解析步骤直接出 HTML。
小结
Comark 用“一次解析、到处渲染”的文档模型,把博客、邮件、RSS 三条分发渠道统一到同一份 Markdown 源稿上:框架渲染器管网页,@comark/html 管静态输出。对于需要多渠道路达的内容团队,这套机制能显著减少模板重复和维护成本。更多实战场景可参考 docs/content/10.use-cases/ 下的使用指南。


