渲染管线总览(remark / rehype 插件链)
Mizuki 的 Markdown 渲染管线是在 astro.config.mjs 中通过 @astrojs/markdown-remark 导出的 unified() 处理器显式组装的一条 remark(MDAST)→ rehype(HAST)插件链,共接入约 12 个 remark 插件与 10 个 rehype 插件,其中近半数为仓库内 src/plugins/ 下的自定义插件,负责数学公式、Wiki 链接、Mermaid/PlantUML 图表、GitHub 风格提示框、图片网格、章节化等站内扩展语法。
目的与范围(Purpose and Scope)
本页是渲染管线的总览页:它回答"一篇 Markdown 从进入 Astro 到产出最终 HTML 之间经历了哪些阶段、插件按什么顺序排列、为什么是这个顺序、哪些插件受配置门控"这一整体问题。
本页覆盖:
- 管线入口:
astro.config.mjs中的markdown.processor = unified({...})配置块; - remark 阶段(语法解析 / MDAST 变换)插件链的完整顺序与设计意图;
- rehype 阶段(HAST / HTML 变换)插件链的完整顺序与设计意图;
- 条件启用插件的配置门控(
markdownConfig、expressiveCodeConfig); - 与 unified 管线并行的 Expressive Code 代码块渲染集成;
- 提示框(Admonition)组件映射表;
- 构建(静态输出)视角下的整体渲染流程与运维注意点。
本页不深入各个插件的内部实现——每个自定义插件(如 remark-wiki-link.mjs、rehype-mermaid.mjs、remark-plantuml.mjs 等)的内部算法、失败模式与扩展点属于其各自的专题页面,本页仅说明它们在链条中的位置与协作关系。代码块主题样式细节见 Expressive Code 相关章节;内容源(src/content/)的组织方式见内容采集(content collections)相关页面。
概述(Overview)
设计动机
Mizuki 是一个 Astro 静态站点(output: "static",astro.config.mjs L109),所有 Markdown 在构建期被完整渲染成 HTML。站点同时面向中文/日文技术写作场景,需要支持:
- 数学公式:
remark-math解析$$...$$/$...$,rehype-katex输出 KaTeX HTML,并通过导入katex/dist/contrib/mhchem.mjs扩展化学方程式语法(astro.config.mjs L13); - Obsidian 风格 Wiki 链接与反链:
remarkWikiLink(可配置开关); - 图表即代码:Mermaid(
remarkMermaid+rehypeMermaid两阶段)与 PlantUML(remarkPlantuml+rehypePlantuml两阶段,配套src/plugins/plantuml-encoder.mjs与src/plugins/mermaid-render-script.js辅助脚本); - GitHub 风格提示框:
remarkFixGithubAdmonitions归一化 GitHub 写法 →remarkDirective提供:::note指令语法 →parseDirectiveNode将指令节点交给 rehype 阶段的rehypeComponents渲染成AdmonitionComponent; - 章节化与锚点导航:
remarkSectionize把标题层级包成<section>,remarkMarkSectionized打标记,rehypeSlug生成标题 id,rehypeAutolinkHeadings追加锚点图标; - 图片与外链治理:
rehypeContentLinks统一外链target=_blank与rel属性,rehypeMarkdownImages处理 Markdown 图片的 referer 策略,remarkAutoImageGrid自动将连续图片组合为网格。
关键术语
| 术语 | 含义 |
|---|---|
| unified 处理器 | 由 @astrojs/markdown-remark 导出的 unified(),Astro 用它替代默认 Markdown 配置,允许完全控制插件链 |
| remark 插件 | 作用于 MDAST(Markdown 抽象语法树)的变换,负责"语法扩展"——识别新语法并生成节点 |
| rehype 插件 | 作用于 HAST(HTML 抽象语法树)的变换,负责"输出塑形"——生成 HTML 元素、属性与组件 |
| 两阶段插件 | 同一能力的 remark 阶段负责"标记"(识别语言为 mermaid 的代码块并改写节点),rehype 阶段负责"渲染"(调用渲染器产出 SVG/图片),Mermaid 与 PlantUML 均采用此模式 |
| 配置门控 | 插件以展开运算符 ...(flag ? [plugin] : []) 方式按需注入,开关集中在 src/config/index.ts 导出的 markdownConfig / expressiveCodeConfig |
架构(Architecture)
架构说明:
-
单一装配点:整条管线只在 astro.config.mjs L211-L320 的
markdown字段中装配一次,Astro 的内容集合渲染(无论.md还是经mdx()集成的 MDX)都复用这一处理器。所有插件在文件头部集中导入(astro.config.mjs L1-L50),其中 13 个来自src/plugins/本地目录。 -
阶段职责分离:remark 阶段解决"作者能写什么"(数学、指令、Wiki 链接、图表代码块),rehype 阶段解决"读者看到什么"(KaTeX HTML、组件、锚点、链接属性)。这遵循 unified 生态的标准分层,也让同一能力可以拆成两个插件在不同时机介入。
-
两条并行渲染通道:Markdown 正文走 unified 链,而代码块由
expressiveCode()Astro 集成(astro.config.mjs L147-L204)在 unified 链之外以 Expressive Code 引擎渲染,两线在 Astro 输出阶段汇合。注意rehypeMermaid/rehypePlantuml抢在代码块进入通用代码渲染之前就把mermaid/plantuml语言块转换走,这是插件排序上的关键约束之一。 -
配置驱动:
markdownConfig.wikiLink、markdownConfig.autoImageGrid、expressiveCodeConfig.codeGroup三处门控决定插件是否注入;permalinkConfig、siteConfig.siteURL、siteConfig.imageOptimization.noReferrerDomains等运行时参数从src/config/index.ts传入各插件。
remark 阶段:插件链逐项解析
remark 数组的顺序(astro.config.mjs L213-L241)是刻意安排的,理解每一步的输入输出才能安全地插入新插件:
1remarkPlugins: [
2 remarkMath,
3 remarkContent,
4 remarkFixGithubAdmonitions,
5 remarkDirective,
6 remarkEscapeNumericColons,
7 ...(markdownConfig.wikiLink.enable
8 ? [
9 [
10 remarkWikiLink,
11 {
12 ...markdownConfig.wikiLink,
13 permalink: permalinkConfig,
14 imageApi: siteConfig.banner.imageApi,
15 noReferrerDomains:
16 siteConfig.imageOptimization?.noReferrerDomains ?? [],
17 },
18 ],
19 ]
20 : []),
21 ...(markdownConfig.autoImageGrid.enable
22 ? [[remarkAutoImageGrid, markdownConfig.autoImageGrid]]
23 : []),
24 parseDirectiveNode,
25 remarkMermaid,
26 [remarkPlantuml, markdownConfig.plantuml],
27 remarkSectionize,
28 remarkMarkSectionized,
29],Source: astro.config.mjs
| 顺序 | 插件 | 来源 | 职责与排序理由 |
|---|---|---|---|
| 1 | remarkMath | 社区包 | 最先运行:把 $...$ / $$...$$ 解析为 math 节点,供 rehype 阶段 rehypeKatex 消费。先于其它文本级插件运行可避免 $ 被后续变换误改写 |
| 2 | remarkContent | 本地 | 站内内容级预处理(src/plugins/remark-content.mjs) |
| 3 | remarkFixGithubAdmonitions | 本地 | 把 GitHub 的 > [!NOTE] 引用式提示框归一化为 :::note 指令写法,因此必须排在 remarkDirective 之前 |
| 4 | remarkDirective | 社区包 | 启用 :::name 指令语法,产生 directive 节点 |
| 5 | remarkEscapeNumericColons | 本地 | 转义数字冒号(如时间 12:30),防止被数学/其它语法误吞 |
| 6 | remarkWikiLink(门控) | 本地 | 识别 [[WikiLink]];注入 permalink 规则、图片 API 地址与 no-referrer 域名列表 |
| 7 | remarkAutoImageGrid(门控) | 本地 | 将连续图片自动分组成网格 |
| 8 | parseDirectiveNode | 本地 | 把 directive 节点转成可被 rehype 阶段 rehypeComponents 接管的元素,是连接两个阶段的桥 |
| 9 | remarkMermaid | 本地 | 标记 mermaid 代码块,改写节点为 rehype 阶段可识别的形态 |
| 10 | remarkPlantuml | 本地 | 同上,针对 plantuml 代码块;接收 markdownConfig.plantuml 配置 |
| 11 | remarkSectionize | 社区包 | 按标题层级将文档包成嵌套 <section> |
| 12 | remarkMarkSectionized | 本地 | 为被 sectionize 的节点打标记,供下游(样式/导航)识别 |
设计要点:remarkFixGithubAdmonitions → remarkDirective → parseDirectiveNode 三步构成"GitHub 提示框兼容 + 指令桥接"的完整链路;Mermaid/PlantUML 的 remark 端必须先于 sectionize 系列运行,确保代码块节点在结构化改写前已被识别。
rehype 阶段:插件链逐项解析
rehype 数组(astro.config.mjs L242-L318)从 MDAST→HAST 转换后的 HTML 树开始工作:
1rehypePlugins: [
2 rehypeKatex,
3 [rehypeContentLinks, { siteUrl: siteConfig.siteURL, target: "_blank",
4 rel: ["nofollow", "noopener", "noreferrer"] }],
5 rehypeSlug,
6 ...(expressiveCodeConfig.codeGroup.enable ? [rehypeCodeGroup] : []),
7 rehypeWrapTable,
8 rehypeMermaid,
9 rehypePlantuml,
10 [rehypeComponents, { components: { github: GithubCardComponent, grid: ImageGridComponent, /* ... */ } }],
11 [rehypeAutolinkHeadings, { behavior: "append", /* ... */ }],
12 [rehypeMarkdownImages, { noReferrerDomains: siteConfig.imageOptimization?.noReferrerDomains ?? [] }],
13],Source: astro.config.mjs
| 顺序 | 插件 | 职责 |
|---|---|---|
| 1 | rehypeKatex | 消费 remark 阶段 math 节点,产出 KaTeX HTML |
| 2 | rehypeContentLinks(本地) | 站内外链判定(基于 siteUrl),统一 target=_blank 与 rel="nofollow noopener noreferrer" |
| 3 | rehypeSlug | 为标题生成稳定 id——必须在 autolink 之前 |
| 4 | rehypeCodeGroup(门控) | 处理代码组(code group)语法 |
| 5 | rehypeWrapTable(本地) | 包裹表格元素以支持横向滚动/样式 |
| 6 | rehypeMermaid(本地) | 渲染 Mermaid 图(配合 src/plugins/mermaid-render-script.js) |
| 7 | rehypePlantuml(本地) | 渲染 PlantUML 图(编码逻辑在 src/plugins/plantuml-encoder.mjs) |
| 8 | rehypeComponents | 组件注入中枢:把自定义元素名映射到本地组件(见下节) |
| 9 | rehypeAutolinkHeadings | 在标题后追加 # 锚点图标 span |
| 10 | rehypeMarkdownImages(本地) | 图片 referer 策略处理 |
提示框组件映射表
rehypeComponents 的 components 映射把约 30 种 GitHub 提示框关键词归一到 5 种视觉类型(astro.config.mjs L257-L292):
1components: {
2 github: GithubCardComponent,
3 grid: ImageGridComponent,
4 note: (x, y) => AdmonitionComponent(x, y, "note"),
5 tip: (x, y) => AdmonitionComponent(x, y, "tip"),
6 important: (x, y) => AdmonitionComponent(x, y, "important"),
7 caution: (x, y) => AdmonitionComponent(x, y, "caution"),
8 warning: (x, y) => AdmonitionComponent(x, y, "warning"),
9 info: (x, y) => AdmonitionComponent(x, y, "note"),
10 success: (x, y) => AdmonitionComponent(x, y, "tip"),
11 question: (x, y) => AdmonitionComponent(x, y, "important"),
12 danger: (x, y) => AdmonitionComponent(x, y, "caution"),
13 example: (x, y) => AdmonitionComponent(x, y, "note"),
14 // ...完整映射见源文件
15},Source: astro.config.mjs
归一规则:info/abstract/summary/tldr/todo → note;hint/success/check/done → tip;question/help/faq → important;attention/failure/fail/missing/danger/error/bug → caution;example/quote/cite → note。这种"多入口词 → 少视觉类型"的收敛设计让作者可以沿用 GitHub 习惯写法,而站点只维护 5 套样式。
核心流程:一篇文档的端到端渲染
流程说明:Astro 静态构建时对每个内容条目调用该处理器。remark 阶段完成全部"语法层"改写后,unified 内置的 mdast-to-hast 转换把 Markdown 树映射成 HTML 树;rehype 阶段完成"输出层"塑形;rehypeComponents 是唯一的组件注入点。代码块不走这条 HAST 路径——Expressive Code 作为 Astro 集成在更高层级接管代码块,因此两阶段插件不必感知代码高亮细节,只需保证 mermaid / plantuml 块在进入通用代码渲染前已被 rehypeMermaid / rehypePlantuml 转换。
配置选项
管线本身没有独立的配置文件;所有开关与参数通过 src/config/index.ts 导出的配置对象注入(在 astro.config.mjs L24-L29 导入):
| 配置项 | 类型/默认值 | 作用 | 影响的插件 |
|---|---|---|---|
markdownConfig.wikiLink.enable | boolean | 是否启用 Wiki 链接语法 | remarkWikiLink |
markdownConfig.wikiLink(展开) | object | Wiki 链接选项(含 permalink、imageApi、noReferrerDomains 覆盖) | remarkWikiLink |
markdownConfig.autoImageGrid.enable | boolean | 是否自动将连续图片分组 | remarkAutoImageGrid |
markdownConfig.plantuml | object | PlantUML 渲染参数(服务端点等) | remarkPlantuml |
expressiveCodeConfig.codeGroup.enable | boolean | 是否启用代码组 | rehypeCodeGroup |
expressiveCodeConfig.defaultWrap | boolean | 代码块默认换行行为 | Expressive Code |
siteConfig.siteURL | string | 站点地址,用于内外链判定 | rehypeContentLinks |
siteConfig.imageOptimization.noReferrerDomains | string[](默认 []) | 需要 no-referrer 的图片域名 | remarkWikiLink、rehypeMarkdownImages |
permalinkConfig | object | 永久链接规则 | remarkWikiLink |
siteConfig.banner.imageApi | string | 图片 API 端点 | remarkWikiLink |
API 参考(管线装配接口)
unified({ remarkPlugins, rehypePlugins })
来自 @astrojs/markdown-remark 的处理器工厂(astro.config.mjs L1、L212)。
参数:
remarkPlugins(Plugin[]):作用于 MDAST 的插件数组,元素可以是裸插件或[plugin, options]元组rehypePlugins(Plugin[]):作用于 HAST 的插件数组,同上
返回: Astro 可直接用作 markdown.processor 的处理器配置对象,替代 Astro 内置的默认 remark/rehype 预设。
说明: 由于显式传入 processor,Astro 默认的 gfm、smartypants 等内置行为不再自动生效——所需能力(如 GitHub 提示框)必须由自定义插件链自行提供,这正是 remarkFixGithubAdmonitions 存在的原因之一。
失败模式、边界与并发
基于装配代码可确认的约束:
- 插件顺序即正确性:
remarkFixGithubAdmonitions若移到remarkDirective之后,GitHub 写法将不被归一化;rehypeSlug若移到rehypeAutolinkHeadings之后,锚点将找不到目标 id。调整顺序前需核对输入依赖。 - 门控插件缺失的静默降级:关闭
wikiLink.enable后,[[...]]语法会以原文形式出现在正文里(无报错)——这是特性开关的预期行为,但在内容库混用旧语法时容易造成"部分页面链接失效"的观感。 - Mermaid/PlantUML 构建期渲染:两阶段设计意味着图表渲染失败发生在构建期而非运行期,错误会让构建直接失败(fail-fast),避免线上出现空图。渲染脚本位于
src/plugins/mermaid-render-script.js与src/plugins/plantuml-encoder.mjs(本页不展开其内部实现)。 - 静态输出一致性:
output: "static"+compressHTML: true(astro.config.mjs L107-L109)意味着管线输出在构建后不可变,任何插件行为差异都会直接体现在产物 diff 中,适合用构建产物对比做回归验证。 - 并发:Astro 对多页面并发调用同一处理器实例;插件应保持无状态(未在装配代码中观察到共享可变状态)。
性能与运维要点
- 代码块是主要热点:Expressive Code 的
styleOverrides全量定制(astro.config.mjs L176-L200)在构建期编译,不影响运行时;但大文档大量图表会显著拉长构建时间。 - shell 类语言禁用行号:
overridesByLang中对shellsession设showLineNumbers: false,对bash/shell/sh/zsh设frame: "code"(astro.config.mjs L168-L174),减少终端风格开销。 - HTML 压缩:
compressHTML: true对管线输出的 HTML 做压缩,配合assetsInlineLimit: 4096控制产物体积(astro.config.mjs L351-L353)。
扩展点
新增渲染能力的标准做法(与现有插件一致):
- 在
src/plugins/新建remark-<feature>.mjs与(若需 HTML 塑形)rehype-<feature>.mjs; - 在 astro.config.mjs 头部导入;
- 按输入依赖插入对应数组位置(新语法尽量靠 remark 前段,新输出形态插在
rehypeComponents附近); - 若需要开关,在
src/config/index.ts增加markdownConfig.<feature>并用展开运算符条件注入; - 若渲染自定义元素,注册到
rehypeComponents的components映射。
相关链接
- 插件实现目录:src/plugins/ —— 共 20 个文件,包括
remark-wiki-link.mjs、remark-mermaid.js、rehype-mermaid.mjs、remark-plantuml.mjs、rehype-plantuml.mjs、rehype-component-admonition.mjs、rehype-component-github-card.mjs、rehype-component-image-grid.mjs等,各自的内部实现见其专题页面 - 管线装配源文件:astro.config.mjs
- 站点内容渲染的作者视角文档:docs/CONTENT_RENDERING.md
- 内容创作语法指南:docs/CONTENT_AUTHORING.zh.md