Markdown
使用 Bun 内置的 Markdown API 解析并渲染 Markdown,支持 GFM 扩展和自定义渲染回调
Bun 内置了一个使用 Rust 编写的快速 Markdown 解析器。它支持 GitHub 风格 Markdown(GFM)扩展,并提供三个 API:
Bun.markdown.html()— 将 Markdown 渲染为 HTML 字符串Bun.markdown.render()— 使用自定义回调渲染 Markdown 的每个元素Bun.markdown.react()— 将 Markdown 渲染为 React JSX 元素
Bun.markdown.html()
将 Markdown 字符串转换为 HTML。
const html = Bun.markdown.html("# Hello **world**");
// "<h1>Hello <strong>world</strong></h1>\n"
默认启用 GFM 扩展,如表格、删除线和任务列表:
const html = Bun.markdown.html(`
| Feature | Status |
|-------------|--------|
| Tables | ~~done~~ |
| Strikethrough| ~~done~~ |
| Task lists | done |
`);
选项
作为第二个参数传入选项对象以配置解析器:
const html = Bun.markdown.html("some markdown", {
tables: true, // GFM 表格(默认:true)
strikethrough: true, // GFM 删除线(默认:true)
tasklists: true, // GFM 任务列表(默认:true)
tagFilter: true, // 过滤不允许的 HTML 标签
autolinks: true, // 自动链接 URL、邮箱和 www. 开头的链接
});
所有可用选项:
| 选项 | 默认值 | 描述 |
|---|---|---|
tables | true | GFM 表格 |
strikethrough | true | GFM 删除线(~~text~~) |
tasklists | true | GFM 任务列表(- [x] item) |
autolinks | false | 启用自动链接——参见自动链接 |
headings | false | 标题 ID 和自动链接——参见标题 ID |
hardSoftBreaks | false | 将软换行视为硬换行 |
wikiLinks | false | 启用 [[wiki links]] |
underline | false | 将 __text__ 渲染为 <u> 而不是 <strong> |
latexMath | false | 启用 $inline$ 和 $$display$$ 数学公式 |
collapseWhitespace | false | 合并文本中的空白 |
permissiveAtxHeaders | false | 允许 # 后没有空格的 ATX 标题 |
noIndentedCodeBlocks | false | 禁用缩进代码块 |
noHtmlBlocks | false | 禁用 HTML 块 |
noHtmlSpans | false | 禁用行内 HTML |
tagFilter | false | 用于过滤不允许 HTML 标签的 GFM 标签过滤器 |
自动链接
传入 true 可启用所有自动链接类型,或者传入对象以精细控制:
// 启用所有自动链接(URL、WWW、邮箱)
Bun.markdown.html("Visit www.example.com", { autolinks: true });
// 仅启用特定类型
Bun.markdown.html("Visit www.example.com", {
autolinks: { url: true, www: true },
});
标题 ID
传入 true 可同时启用标题 ID 和自动链接标题,或者传入对象进行细粒度控制:
// 启用标题 ID 和自动链接标题
Bun.markdown.html("## Hello World", { headings: true });
// '<h2 id="hello-world"><a href="#hello-world">Hello World</a></h2>\n'
// 仅启用标题 ID(无自动链接)
Bun.markdown.html("## Hello World", { headings: { ids: true } });
// '<h2 id="hello-world">Hello World</h2>\n'
Bun.markdown.render()
解析 Markdown 并使用自定义 JavaScript 回调进行渲染。这些回调让你可以完全控制输出格式。你可以生成带有自定义类的 HTML、ANSI 终端输出,或任何其他字符串格式。
const result = Bun.markdown.render("# Hello **world**", {
heading: (children, { level }) => `<h${level} class="title">${children}</h${level}>`,
strong: children => `<b>${children}</b>`,
paragraph: children => `<p>${children}</p>`,
});
// '<h1 class="title">Hello <b>world</b></h1>'
回调函数签名
每个回调接收:
children— 元素累积的内容字符串meta(可选)— 带有元素特定元数据的对象
返回一个字符串以替换元素的渲染结果。返回 null 或 undefined 可完全从输出中省略该元素。如果元素没有回调,其子元素将原样传递。
块级回调
| 回调 | 元数据 | 描述 |
|---|---|---|
heading | { level: number, id?: string } | 标题级别 1–6。启用 headings: { ids: true } 时会设置 id |
paragraph | — | 段落块 |
blockquote | — | 引用块 |
code | { language?: string } | 围栏代码或缩进代码块。language 是围栏信息字符串(若指定) |
list | { ordered: boolean, start?: number } | 有序或无序列表。有序列表的起始数字为 start |
listItem | { checked?: boolean } | 列表项。任务列表项(- [x] / - [ ])会设置 checked |
hr | — | 分割线 |
table | — | 表格块 |
thead | — | 表头 |
tbody | — | 表格主体 |
tr | — | 表格行 |
th | { align?: "left" | "center" | "right" } | 表头单元格。指定对齐时设置 align |
td | { align?: "left" | "center" | "right" } | 表格单元格。指定对齐时设置 align |
html | — | 原始 HTML 内容 |
列表项元数据
listItem 回调会接收渲染标记所需的所有信息:
index— 在父列表中的基于 0 的位置depth— 父列表的嵌套层级(0 = 顶层)ordered— 父列表是否为有序列表start— 父列表的起始编号(仅当ordered为 true 时)checked— 任务列表状态(仅用于- [x]/- [ ]项)
行内回调
| 回调 | 元数据 | 描述 |
|---|---|---|
strong | — | 加粗强调 (**text**) |
emphasis | — | 斜体强调 (*text*) |
link | { href: string, title?: string } | 链接 |
image | { src: string, title?: string } | 图像 |
codespan | — | 行内代码 (`code`) |
strikethrough | — | 删除线 (~~text~~) |
text | — | 纯文本内容 |
示例
带类的自定义 HTML
const html = Bun.markdown.render("# Title\n\nHello **world**", {
heading: (children, { level }) => `<h${level} class="heading heading-${level}">${children}</h${level}>`,
paragraph: children => `<p class="body">${children}</p>`,
strong: children => `<strong class="bold">${children}</strong>`,
});
去除所有格式
const plaintext = Bun.markdown.render("# Hello **world**", {
heading: children => children,
paragraph: children => children,
strong: children => children,
emphasis: children => children,
link: children => children,
image: () => "",
code: children => children,
codespan: children => children,
});
// "Hello world"
忽略元素
返回 null 或 undefined 可从输出中删除某元素:
const result = Bun.markdown.render("# Title\n\n\n\nHello", {
image: () => null, // 移除所有图片
heading: children => children,
paragraph: children => children + "\n",
});
// "Title\nHello\n"
ANSI 终端输出
const ansi = Bun.markdown.render("# Hello\n\nThis is **bold** and *italic*", {
heading: (children, { level }) => `\x1b[1;4m${children}\x1b[0m\n`,
paragraph: children => children + "\n",
strong: children => `\x1b[1m${children}\x1b[22m`,
emphasis: children => `\x1b[3m${children}\x1b[23m`,
});
嵌套列表编号
listItem回调函数接收渲染标记所需的所有内容——无需后期处理:
const result = Bun.markdown.render("1. first\n 1. sub-a\n 2. sub-b\n2. second", {
listItem: (children, { index, depth, ordered, start }) => {
const n = (start ?? 1) + index;
// 深度 0 使用 1. 2. 3.,深度 1 使用 a. b. c.,深度 2 使用 i. ii. iii.
const marker = !ordered
? "-"
: depth === 0
? `${n}.`
: depth === 1
? `${String.fromCharCode(96 + n)}.`
: `${toRoman(n)}.`;
return " ".repeat(depth) + marker + " " + children.trimEnd() + "\n";
},
// 添加换行符,使嵌套列表与其父列表项的文本分隔开
list: children => "\n" + children,
});
// 1. first
// a. sub-a
// b. sub-b
// 2. second
代码块语法高亮
const result = Bun.markdown.render("```js\nconsole.log('hi')\n```", {
code: (children, meta) => {
const lang = meta?.language ?? "";
return `<pre><code class="language-${lang}">${children}</code></pre>`;
},
});
解析器选项
将解析器选项作为单独的第三个参数传入:
const result = Bun.markdown.render(
"Visit www.example.com",
{
link: (children, { href }) => `[${children}](${href})`,
paragraph: children => children,
},
{ autolinks: true },
);
Bun.markdown.react()
将 Markdown 直接渲染为 React 元素。返回一个 <Fragment>,可以作为组件返回值使用。
function Markdown({ text }: { text: string }) {
return Bun.markdown.react(text);
}
服务端渲染
支持 renderToString() 和 React 服务端组件:
import { renderToString } from "react-dom/server";
const html = renderToString(Bun.markdown.react("# Hello **world**"));
// "<h1>Hello <strong>world</strong></h1>"
组件替代
通过第二个参数传入以标签名为键的自定义 React 组件,替换任意 HTML 元素:
function Code({ language, children }) {
return (
<pre data-language={language}>
<code>{children}</code>
</pre>
);
}
function Link({ href, title, children }) {
return (
<a href={href} title={title} target="_blank" rel="noopener noreferrer">
{children}
</a>
);
}
function Heading({ id, children }) {
return (
<h2 id={id}>
<a href={`#${id}`}>{children}</a>
</h2>
);
}
const el = Bun.markdown.react(
content,
{
pre: Code,
a: Link,
h2: Heading,
},
{ headings: { ids: true } },
);
可覆盖元素列表
你可以覆盖解析器生成的每个 HTML 标签:
| 选项 | 属性 | 描述 |
|---|---|---|
h1–h6 | { id?, children } | 标题。启用 headings: { ids: true } 时带有 id |
p | { children } | 段落 |
blockquote | { children } | 引用 |
pre | { language?, children } | 代码块。language 是信息字符串(例如 "js") |
hr | {} | 分割线(无子元素) |
ul | { children } | 无序列表 |
ol | { start, children } | 有序列表。start 是起始编号 |
li | { checked?, children } | 列表项。任务列表项带 checked |
table | { children } | 表格 |
thead | { children } | 表头 |
tbody | { children } | 表体 |
tr | { children } | 表格行 |
th | { align?, children } | 表头单元格 |
td | { align?, children } | 表格单元格 |
em | { children } | 斜体强调 (*text*) |
strong | { children } | 加粗 (**text**) |
a | { href, title?, children } | 链接 |
img | { src, alt?, title? } | 图像(无子元素) |
code | { children } | 行内代码 |
del | { children } | 删除线 (~~text~~) |
br | {} | 硬换行(无子元素) |
React 18 及更早版本
默认情况下,元素使用 Symbol.for('react.transitional.element') 作为 $$typeof 符号。对于 React 18 及更早版本,在选项(第三参数)中传入 reactVersion: 18:
function Markdown({ text }: { text: string }) {
return Bun.markdown.react(text, undefined, { reactVersion: 18 });
}
解析器选项
将任意解析器选项作为第三个参数传入:
const el = Bun.markdown.react("## Hello World", undefined, {
headings: { ids: true },
autolinks: true,
});