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