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