Skip to main content
不稳定的 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. 开头的链接
});
所有可用选项:
选项默认值描述
tablesfalseGFM 表格
strikethroughfalseGFM 删除线 (~~text~~)
tasklistsfalseGFM 任务列表 (- [x] item)
autolinksfalse启用自动链接 — 参见 自动链接
headingsfalse启用标题 ID 和自动链接 — 参见 标题 ID
hardSoftBreaksfalse把软换行视为硬换行
wikiLinksfalse启用 [[wiki links]]
underlinefalse__text__ 渲染为 <u> 而非 <strong>
latexMathfalse启用 $inline$$$display$$ 数学模式
collapseWhitespacefalse折叠文本中的空白
permissiveAtxHeadersfalse允许 ATX 标题在 # 之后无空格
noIndentedCodeBlocksfalse禁用缩进代码块
noHtmlBlocksfalse禁用 HTML 块
noHtmlSpansfalse禁用内联 HTML
tagFilterfalse过滤不允许的 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>'

回调函数签名

每个回调接收:
  1. children — 元素累积的内容字符串
  2. meta(可选)— 带有元素特定元数据的对象
返回一个字符串替代元素渲染。返回 nullundefined 则完全忽略该元素。如果未注册元素回调,则其子元素会原样通过。

块级回调

回调元数据描述
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"

忽略元素

返回 nullundefined 可从输出中删除某元素:
const result = Bun.markdown.render("# Title\n\n![logo](img.png)\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描述
h1h6{ 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,
});