运行时包管理器打包器测试运行器代码检查格式化指南参考博客反馈

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。

TypeScript
const html = Bun.markdown.html("# Hello **world**");
// "<h1>Hello <strong>world</strong></h1>\n"

默认启用 GFM 扩展,如表格、删除线和任务列表:

TypeScript
const html = Bun.markdown.html(`
| Feature      | Status |
|-------------|--------|
| Tables       | ~~done~~ |
| Strikethrough| ~~done~~ |
| Task lists   | done |
`);

选项

作为第二个参数传入选项对象以配置解析器:

TypeScript
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. 开头的链接
});

所有可用选项:

选项默认值描述
tablestrueGFM 表格
strikethroughtrueGFM 删除线(~~text~~)
taskliststrueGFM 任务列表(- [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 标签的 GFM 标签过滤器

自动链接

传入 true 可启用所有自动链接类型,或者传入对象以精细控制:

TypeScript
// 启用所有自动链接(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 和自动链接标题,或者传入对象进行细粒度控制:

TypeScript
// 启用标题 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 终端输出,或任何其他字符串格式。

TypeScript
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(可选)— 带有元素特定元数据的对象

返回一个字符串以替换元素的渲染结果。返回 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

TypeScript
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>`,
});

去除所有格式

TypeScript
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 可从输出中删除某元素:

TypeScript
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 终端输出

TypeScript
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回调函数接收渲染标记所需的所有内容——无需后期处理:

TypeScript
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

代码块语法高亮

TypeScript
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>`;
  },
});

解析器选项

将解析器选项作为单独的第三个参数传入:

TypeScript
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>,可以作为组件返回值使用。

TSX
function Markdown({ text }: { text: string }) {
  return Bun.markdown.react(text);
}

服务端渲染

支持 renderToString() 和 React 服务端组件:

TSX
import { renderToString } from "react-dom/server";

const html = renderToString(Bun.markdown.react("# Hello **world**"));
// "<h1>Hello <strong>world</strong></h1>"

组件替代

通过第二个参数传入以标签名为键的自定义 React 组件,替换任意 HTML 元素:

TSX
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:

TSX
function Markdown({ text }: { text: string }) {
  return Bun.markdown.react(text, undefined, { reactVersion: 18 });
}

解析器选项

将任意解析器选项作为第三个参数传入:

TSX
const el = Bun.markdown.react("## Hello World", undefined, {
  headings: { ids: true },
  autolinks: true,
});