Remark plugins
@rho/md的 7 个 build-time remark plugin 详解——什么时候用、各自做什么、生成什么 HTML。
总览
| Plugin | 处理 DSL | 输出 HTML 含 marker | 是否需要 hydration |
|---|---|---|---|
remarkCallout |
Callout (> [!INFO]) |
<aside class="rho-callout rho-callout-info"> |
❌ 纯静态 |
remarkLayout |
Layout (```layout) |
<div class="rho-layout-grid" data-cols="3"> |
❌ 纯静态 |
remarkAnnotate |
Annotate (```annotate) |
<div class="rho-annotate"> + <span class="rho-anno"> |
❌ 纯静态(hover tooltip 通过 CSS) |
remarkTabs |
Tabs (```tabs) |
<div class="rho-tabs"> |
✅ renderTabsBlocks |
remarkStepper |
Stepper (```stepper) |
<div class="rho-stepper"> |
✅ renderStepperBlocks |
remarkTimeline |
Timeline (```timeline) |
<ol class="rho-timeline"> |
❌ 纯静态 |
remarkModal |
Modal (```modal) |
<a class="rho-modal-trigger"> + <dialog class="rho-modal"> |
✅ renderModalBlocks |
interact 块(含 vega / svg / template / computed / timer)不在 remark plugins 里——它们的 fenced
```interact块在 unified 默认转<pre><code>,被renderInteractBlocks通过 lang label 识别后接管。
单独 vs 全部注册
全部注册(推荐)
import { remarkPlugins } from '@rho/md';
remarkPlugins.forEach((p) => processor.use(p));
这是 [remarkCallout, remarkLayout, remarkAnnotate, remarkTabs, remarkStepper, remarkTimeline, remarkModal] 数组。
选择性注册(高级)
如果你只想支持部分 DSL:
import { remarkCallout, remarkLayout } from '@rho/md';
processor
.use(remarkCallout)
.use(remarkLayout);
// 不注册其他 → tabs / stepper / timeline / modal / annotate 不被识别
注意:选择性注册时还是要调
registerAllInnerPlugins(),否则嵌套已注册的 plugin 也不工作。要严格隔离请用registerInnerPlugin(plugin)单独注册。
各 plugin 详解
remarkCallout
触发:blockquote 第一行匹配 [!TYPE]。
输入:
> [!INFO]
> Body
> Multi-line.
输出:
<aside class="rho-callout rho-callout-info" data-type="INFO">
<header class="rho-callout-header">
<span class="rho-callout-icon">ℹ️</span>
<span class="rho-callout-title">INFO</span>
</header>
<div class="rho-callout-body">
<p>Body</p>
<p>Multi-line.</p>
</div>
</aside>
支持类型:INFO / WARN / ZEN(Rho 原生)+ NOTE / TIP / IMPORTANT / WARNING / CAUTION(GFM)。详见 Callout (writer).
Hydration:无需。
remarkLayout
触发:fenced block 含 lang layout grid cols=N。
输入:
\```layout grid cols=2
:::card accent=blue
A
:::
:::card accent=red
B
:::
\```
输出:
<div class="rho-layout-grid" data-cols="2">
<article class="rho-card rho-card-blue" data-accent="blue">
<p>A</p>
</article>
<article class="rho-card rho-card-red" data-accent="red">
<p>B</p>
</article>
</div>
Hydration:无需。CSS Grid + media query 自动响应式。
remarkAnnotate
触发:fenced block 含 lang annotate。
输入:
\```annotate
The quick brown fox
---
4-9|red|adj
10-15|blue|noun
\```
输出:
<div class="rho-annotate">
The <span class="rho-anno rho-anno-red" data-label="adj" title="adj">quick</span>
<span class="rho-anno rho-anno-blue" data-label="noun" title="noun">brown</span> fox
</div>
Hydration:无需。tooltip 用 CSS :hover + [title]。
remarkTabs
触发:fenced block 含 lang tabs。
输入:
\```tabs default="B"
:::tab title=A
A content
:::
:::tab title=B
B content
:::
\```
输出:
<div class="rho-tabs" data-default="B">
<div class="rho-tabs-bar" role="tablist">
<button class="rho-tab-btn" data-tab-id="A">A</button>
<button class="rho-tab-btn rho-tab-active" data-tab-id="B">B</button>
</div>
<div class="rho-tab-panels">
<div class="rho-tab-panel" data-tab-id="A" hidden>A content</div>
<div class="rho-tab-panel" data-tab-id="B">B content</div>
</div>
</div>
Hydration:renderTabsBlocks(container) 给按钮加 click handler。
remarkStepper
触发:fenced block 含 lang stepper。
输入:
\```stepper linear=true
:::step title="A"
A
:::
:::step title="B"
B
:::
\```
输出:
<div class="rho-stepper" data-linear="true">
<div class="rho-stepper-progress" role="progressbar">
<span class="rho-step-marker rho-step-active" data-step="0">A</span>
<span class="rho-step-marker" data-step="1">B</span>
</div>
<div class="rho-step-panels">
<div class="rho-step-panel" data-step="0">A</div>
<div class="rho-step-panel" data-step="1" hidden>B</div>
</div>
<div class="rho-stepper-nav">
<button class="rho-step-prev" disabled>Prev</button>
<button class="rho-step-next">Next</button>
</div>
</div>
Hydration:renderStepperBlocks(container) 给 Prev/Next 加 click handler,跟踪 active step。
remarkTimeline
触发:fenced block 含 lang timeline。
输入:
\```timeline
:::event date=2026-01-01 title="A"
desc A
:::
:::event date=2026-02-01 title="B"
desc B
:::
\```
输出:
<ol class="rho-timeline">
<li class="rho-event rho-event-blue" data-accent="blue">
<header class="rho-event-header">
<time class="rho-event-date">2026-01-01</time>
<h3 class="rho-event-title">A</h3>
</header>
<div class="rho-event-body">desc A</div>
</li>
<li class="rho-event rho-event-blue" data-accent="blue">
<header class="rho-event-header">
<time class="rho-event-date">2026-02-01</time>
<h3 class="rho-event-title">B</h3>
</header>
<div class="rho-event-body">desc B</div>
</li>
</ol>
Hydration:无需。CSS 处理左右交替布局。
remarkModal
触发:fenced block 含 lang modal trigger="..."。
输入:
\```modal trigger="show details" width=large
hidden content
\```
输出:
<a class="rho-modal-trigger" data-modal-id="m-abc123">show details</a>
<dialog class="rho-modal rho-modal-large" id="m-abc123" data-width="large">
<button class="rho-modal-close" aria-label="Close">✕</button>
<div class="rho-modal-body">hidden content</div>
</dialog>
Hydration:renderModalBlocks(container) 给 trigger 加 click → dialog.showModal(),给 close 加 click → dialog.close()。
自定义 / 扩展
自己写 remark plugin?
@rho/md 不暴露 plugin SDK——但因为底层是标准 unified,你可以混入自己的 remark plugin:
import myCustomPlugin from './my-custom-plugin';
processor
.use(remarkParse)
.use(remarkGfm);
remarkPlugins.forEach((p) => processor.use(p));
processor.use(myCustomPlugin); // 跟 Rho 共存
建议:自定义 plugin 跟 Rho 的 plugin 用不同 fenced lang label 避免冲突(不要叫
interact/layout/ 等)。
关闭某个 plugin
// 不注册 remarkModal → modal blocks 显示为代码块字面量
import { remarkCallout, remarkLayout, remarkTabs, remarkStepper, remarkTimeline, remarkAnnotate } from '@rho/md';
processor
.use(remarkCallout)
.use(remarkLayout)
.use(remarkTabs)
.use(remarkStepper)
.use(remarkTimeline)
.use(remarkAnnotate);
// 注意:还要 registerInnerPlugin 6 个对应的,不要 remarkModal
See also
- Hydration utilities — runtime 把 marker class 接管
- Inner processor — 嵌套 DSL 时 plugin 怎么 cascade
- API reference
- CSS theming — 改 Rho 输出 HTML 的样式