執行環境套件管理器打包器測試執行器程式碼檢查格式化指南參考部落格意見回饋

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