Quick start (unified pipeline)
装完
@rho/md后 5 分钟内跑出第一个 Rho 渲染。
完整最小集成(~30 行)
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Rho demo</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div id="root" class="markdown-body"></div>
<script type="module" src="./main.js"></script>
</body>
</html>
main.js:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import { registerAllInnerPlugins, remarkPlugins, hydrateAll } from '@rho/md';
import '@rho/md/css';
// 1. 注册 inner plugins(嵌套 DSL 必须)
registerAllInnerPlugins();
// 2. 构建 unified processor
const processor = unified()
.use(remarkParse)
.use(remarkGfm);
// 3. 注册 Rho 的 7 个 remark plugins
remarkPlugins.forEach((p) => processor.use(p));
// 4. 转 HTML AST + stringify
processor
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify, { allowDangerousHtml: true });
// 5. 你的 markdown 源
const markdown = `
# Hello Rho
> [!INFO]
> 这是一个 callout
\`\`\`interact
slider x 0 10 5 1
template stl:
[选中值] -> [{x}]
\`\`\`
`;
// 6. process → 注入 DOM
const html = String(await processor.process(markdown));
const container = document.getElementById('root');
container.innerHTML = html;
// 7. hydrate 交互块(slider / tabs / etc.)
hydrateAll(container);
打开 index.html → 你看到:
- 标题 "Hello Rho"
- 蓝色 callout
- 可拖动的 slider + 实时输出 "选中值 = 5"
6 步详解
Step 1:registerAllInnerPlugins()
registerAllInnerPlugins();
做什么:告诉 Rho 的 inner processor "嵌套 DSL 时所有 7 个 remark plugin 都要应用"。 为什么必须:嵌套 callout / layout / interact 等需要 inner processor 知道有哪些 DSL plugin。详见 Inner processor。
常见错误:忘加这行 → layout 内的 callout 显示为字面量。
Step 2-4:构建 unified processor
const processor = unified()
.use(remarkParse)
.use(remarkGfm);
remarkPlugins.forEach((p) => processor.use(p));
processor
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify, { allowDangerousHtml: true });
| Step | 作用 |
|---|---|
remarkParse |
markdown 字符串 → markdown AST |
remarkGfm |
GFM 扩展(task list / table / 等) |
remarkPlugins (7 个) |
Rho 的 callout / layout / tabs / stepper / timeline / annotate / modal 转换 |
remarkRehype |
markdown AST → HTML AST |
rehypeStringify |
HTML AST → HTML 字符串 |
allowDangerousHtml: true必填 —— Rho DSL 转换出的 HTML 含 marker classes / data attributes,被 rehype 视为"dangerous"——必须允许。Rho 的 SVG sanitizer 在内部处理安全。
Step 5:你的 markdown
任何合法 markdown + Rho DSL 都行:
const markdown = `
# 标题
正文段落。
> [!ZEN]
> Rho 的灵魂
\`\`\`layout grid cols=2
:::card accent=blue
左卡内容
:::
:::card accent=red
右卡内容
:::
\`\`\`
\`\`\`interact
slider rate 0 0.15 0.05 0.01
template stl: [利率] -> [{(rate*100):.1f}%]
\`\`\`
`;
Step 6:process → 注入 DOM
const html = String(await processor.process(markdown));
container.innerHTML = html;
String(...) 必须:process() 返回 VFile,String() 取 .value。
Step 7:hydrate
hydrateAll(container);
做什么:扫 container 内所有 Rho marker class(.rho-interact / .rho-tabs / .rho-stepper / .rho-modal),把它们从静态 HTML 变成可交互组件。
必须在浏览器里跑——SSR 的话,把 hydrate 放在 client-side bundle 里。
完整 file structure
my-rho-demo/
├── package.json
├── index.html
├── main.js
└── style.css ← 可选,扩展 Rho 默认样式
package.json:
{
"name": "my-rho-demo",
"version": "1.0.0",
"type": "module",
"dependencies": {
"@rho/md": "^0.1.0",
"unified": "^11.0.5",
"remark-parse": "^11.0.0",
"remark-gfm": "^4.0.1",
"remark-rehype": "^11.0.0",
"rehype-stringify": "^10.0.0",
"vega-embed": "^7.0.0"
}
}
启动:
npm install
npx vite # 或任意支持 ES module 的 dev server
框架集成
React
import { useEffect, useRef } from 'react';
import { /* ... */ } from '@rho/md';
function RhoMarkdown({ source }) {
const ref = useRef(null);
useEffect(() => {
const html = renderRho(source); // 调你的 unified pipeline
ref.current.innerHTML = html;
hydrateAll(ref.current);
}, [source]);
return <div className="markdown-body" ref={ref} />;
}
Vue 3
<template>
<div class="markdown-body" ref="root"></div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { hydrateAll } from '@rho/md';
const props = defineProps(['source']);
const root = ref(null);
const update = async () => {
const html = await renderRho(props.source);
root.value.innerHTML = html;
hydrateAll(root.value);
};
onMounted(update);
watch(() => props.source, update);
</script>
Astro / Next.js / 其他静态站
build 时跑 unified pipeline 生成 HTML,runtime 在 client-side script 里调 hydrateAll。
SSR + hydration
服务端:
// SSR (Node)
const html = String(await processor.process(markdown));
return `<html>...<div id="root">${html}</div>...<script src="/client.js">...`;
客户端 (client.js):
import { hydrateAll } from '@rho/md';
import '@rho/md/css';
hydrateAll(document.getElementById('root'));
Common issues
嵌套 callout 在 layout 里不渲染
→ 忘 registerAllInnerPlugins()。
chart 不渲染但其他都正常
→ 忘装 vega-embed。
slider 不可拖
→ 忘 hydrateAll(container),或 hydrate 跑在 SSR 端而不是 client。
TypeScript 抱怨类型
→ 检查 tsconfig.json 的 module: "esnext" 和 moduleResolution: "bundler"。
接下来
→ API reference — 全 API surface → Hydration utilities — 部分 hydrate / 时机控制 → CSS theming — 改样式 → Inner processor — 嵌套 DSL 详解