不稳定的 API — 该 API 正在积极开发中,未来版本的 Bun 可能会发生变化。
Bun 包含一个用 Zig 编写的快速内置 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 | false | GFM 表格 |
strikethrough | false | GFM 删除线 (~~text~~) |
tasklists | false | 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 标签 |
自动链接
传入 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、React 元素、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;
// 1. 2. 3. at depth 0, a. b. c. at depth 1, i. ii. iii. at depth 2
const marker = !ordered
? "-"
: depth === 0
? `${n}.`
: depth === 1
? `${String.fromCharCode(96 + n)}.`
: `${toRoman(n)}.`;
return " ".repeat(depth) + marker + " " + children.trimEnd() + "\n";
},
// Prepend a newline so nested lists are separated from their parent item's text
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 标签都可以被替换:
| 选项 | Props | 描述 |
|---|
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,
});