marked 和 Bun.markdown 差了整整一个生态:Bun v1.4 用内置 Rust 把 Markdown 解析做进了标准库
**账号:fe_arch(阿柯·前端架构)**
**选题来源:** Bun v1.4(2026-08-20)内置 `Bun.markdown` API,Rust 编写,支持 GFM 三种输出模式
—
## 导语
给项目加 Markdown 渲染,你的 `package.json` 里可能有过这些依赖之一:`marked`、`remark`、`markdown-it`、`showdown`、`markdown-js`。每一个都是几十 KB 到上百 KB 的 npm 包,每一个在你的依赖树里多扎了一层。
2026-08-20,Bun v1.4 发布。Bun 团队把整个 Markdown 解析器用 Rust 重写了一遍,打包进了 Bun 的内置 API:`Bun.markdown.html()`、`Bun.markdown.render()`、`Bun.markdown.react()`——三个接口,分别对应 HTML 字符串、自定义回调渲染、React 组件三种场景。
这次,「内置」不只是省一个包:Rust 实现让解析器在对抗性输入下(恶意构造的超长文档、畸形 Markdown)仍保持线性时间复杂度,这是大多数 JS 实现的盲区。
—
## 前后对比:同一段 Markdown,两种处理方式
**旧方式(marked.js)**
“`javascript
// npm install marked
import { marked } from ‘marked’;
const html = marked(“# Hellonn**world**nn- [x] donen- [ ] todo”);
// 配置 GFM:每个选项都是独立包或插件
marked.setOptions({ gfm: true, breaks: true });
// 渲染结果:HTML 字符串,GFM 任务列表生效
“`
**新方式(Bun.markdown)**
“`javascript
// 无需安装,直接 import
const { markdown } = require(‘bun’);
// 三种输出模式
const html = markdown.html(“# Hellonn**world**”); // HTML 字符串
const react = markdown.react(“- [x] donen- [ ] todo”); // React 组件
const custom = markdown.render(“# Hello **world**”, {
heading: (children, { level }) => `${children}`,
strong: children => `${children}`,
paragraph: children => `
${children}
`,
}); // 自定义回调
“`
| 维度 | marked / remark / markdown-it | `Bun.markdown` |
|——|——————————|—————-|
| npm 包 | 至少 1 个(30~200KB)| **0**,Bun 内置 |
| 依赖传递 | 间接依赖链可能很深 | 无外部依赖 |
| GFM 表格/删除线/任务列表 | 需配置或插件 | **默认启用** |
| React 组件输出 | 需 remark-react 等桥接包 | `markdown.react()` 直接输出 |
| 解析器实现 | JavaScript | Rust(线性时间复杂度)|
| 对抗性输入 | 可能超时或 OOM | 线性时间,有界 |
| `.md` 文件加载 | 需额外 loader | `bun ./README.md` 直接渲染终端 |
—
## 三种 API:每种解决一个具体场景
**场景一:快速生成 HTML 字符串(最常用)**
“`javascript
const html = Bun.markdown.html(`
## Highlights
– ~~Deprecated~~ replaced by new API
– [x] GFM task list item
– Regular paragraph
| Column A | Column B |
|———-|———-|
| Cell 1 | Cell 2 |
`, { tables: true, strikethrough: true, tasklists: true });
console.log(typeof html); // “string”
// 默认启用:GFM 表格、删除线 ~~text~~、任务列表 – [x] / – [ ]
“`
**场景二:React 组件直出(无需 remark-react 桥接)**
“`jsx
import { Bun } from ‘bun’;
function DocPage({ source }) {
// markdown.react() 返回 React 元素,可直接放进 JSX
return (
// 可选:自定义每个 HTML 标签对应的 React 组件
h1: ({ children }) =>
{children}
,
code: ({ children, meta }) => {children},
})}
);
}
“`
remark-react 这类桥接包以前需要同时装 `remark`、`remark-parse`、`remark-react`、`unified` 四个包,现在一个 `Bun.markdown.react()` 全搞定。
**场景三:自定义渲染回调(终端彩色输出、静态网站生成器)**
“`javascript
// 生成终端彩色输出(ANSI)
const result = Bun.markdown.render(“# Hello **world**”, {
heading: (children, { level }) => `x1b[1m${‘#’.repeat(level)} ${children}x1b[0mn`,
strong: children => `x1b[1m${children}x1b[22m`,
paragraph: children => `${children}n`,
});
// 不需要任何 npm 包
console.log(result); // ANSI 彩色输出
“`
Bun v1.3.12 起还支持直接在终端渲染 Markdown 文件:
“`bash
bun ./README.md # 在终端直接渲染 Markdown,带 ANSI 彩色
“`
—
## Rust 实现的具体差异:为什么这次不是「又一个大号 npm 包」
Bun.markdown 解析器用 Rust 编写,这个区别不只是性能数字,而是工程层面的实质差异:
**对抗性输入的处理**
JS 实现的 Markdown 解析器(如 marked)在处理超长嵌套列表或重复的 `>` 引用块时,曾出现过正则回溯导致的 OOM 和 CPU 占用激增。Rust 实现通过手写解析状态机(无正则回溯),解析时间与输入长度严格线性相关。
Bun 官方文档原文:
> `.md` is a bundler loader, and the parser runs in linear time on adversarial input.
**GFM 规范完整覆盖**
GitHub Flavored Markdown 规范包含表格、自动链接(URL/邮箱/www 探测)、任务列表、删除线、围栏代码块等。多数 JS 库默认不全启,需要手动配置。Bun.markdown 默认全部开启,且在 `.md` 文件 loader 路径下自动生效。
—
## 落地路径:三步迁移到 Bun.markdown
**第一步:检查项目里有哪些 Markdown 包(5 分钟)**
“`bash
grep -rn “marked|remark|markdown-it|showdown” package.json package-lock.json | wc -l
“`
若返回 0,则项目不依赖任何 Markdown 包。若返回 >0,记下包名和主要使用场景。
**第二步:按场景替换**
| 场景 | 旧包 | 新写法 |
|——|——|——–|
| 渲染用户输入的 Markdown 评论/文章 | `marked` | `Bun.markdown.html(raw)` |
| 渲染 `.md` 文件为 React 组件 | `remark-react` + `unified` | `Bun.markdown.react(mdString)` |
| 终端彩色渲染 README | `chalk` + 手动格式化 | `bun ./README.md` |
| 静态站点生成器 | `markdown-it` | `Bun.markdown.html()` + 注入自定义样式 |
| 代码高亮 | `highlight.js` + `marked-highlight` | 配合 `Bun.markdown.render()` 回调注入 highlight.js |
**第三步:降级处理(Bun 版本判断)**
“`javascript
// Bun 版本 >= 1.4.0 才内置 markdown API
const [major, minor, patch] = process.versions.bun.split(‘.’).map(Number);
if (major > 1 || (major === 1 && minor >= 4)) {
// 使用内置 API
const html = Bun.markdown.html(source);
} else {
// 降级到 marked
const { marked } = await import(‘marked’);
const html = marked(source);
}
“`
—
## 注意事项
**1. HTML 输出不消毒**
Bun.markdown.html() 的输出**不包含 HTML 消毒**。如果渲染用户提交的 Markdown(评论区、用户文章),仍需要在输出前通过 DOMPurify 或 Bun 的 Sanitizer API 消毒:
“`javascript
const raw = Bun.markdown.html(userMarkdown);
const safe = raw; // TODO: 接入 DOMPurify 或 Sanitizer API
“`
**2. API 标注为 Unstable**
Bun 官方文档明确说明:`Bun.markdown` 是 Unstable API,未来版本可能breaking change。如果在生产环境中使用,建议锁定 Bun 版本(`bun@1.4.x` 而非 `latest`)。
**3. 不是所有 GFM 都默认开启**
autolinks(自动探测 URL/邮箱)、heading IDs、wiki links 等功能需要显式开启:
“`javascript
Bun.markdown.html(md, {
autolinks: true, // 自动将 www.example.com 变成 链接
headings: { ids: true }, // 为标题加 id 属性
wikiLinks: true, // 启用 [[WikiLink]] 语法
});
“`
—
## 浏览器兼容性
Bun.markdown 是 **Bun 运行时独有 API**,不适用于浏览器或 Node.js。在以下场景下仍需 npm 包:
| 环境 | Bun.markdown 可用? | 替代方案 |
|——|———————|———-|
| Bun 运行时(服务端/CLI)| ✅ v1.4+ | — |
| Node.js 26 | ❌ | `marked`、`remark` |
| Deno | ❌ | `markdown`、`deno_markdown` |
| 浏览器 | ❌ | DOMPurify + marked 或专门的 Web 库 |
| 打包产物(Browser)| ❌ | 同上 |
核心价值场景:**Bun 开发的服务端/CLI 工具/打包脚本**,这类场景通常不涉及多运行时兼容,用 Bun.markdown 直接省掉一个外部依赖是合理选择。
—
## 下一步
Bun v1.4.2(2026-09-05)已在生产环境稳定使用。如果项目用 Bun 做后端服务或 CLI 工具:
1. **立即可试**:`bun -e “const { markdown } = require(‘bun’); console.log(markdown.html(‘# test’))”` 验证 API 可用性
2. **渐进替换**:优先在 `.md` 文件渲染、README 终端输出等非关键路径上替换
3. **监控 Bun 版本**:锁定 `bun@1.4.x`,避免 v1.5 升级导致的 breaking change 影响生产
—
评论区
登录后可评论。