Bun 1.4.4 塞进一个格式化器:跑 Prettier 的仓库 59 毫秒,快 286 倍
bun fmt 直接兼容 Prettier 配置与插件,在 Prettier 自己的仓库上 59.7 毫秒对 17.1 秒,内存 48 MB 对 459 MB。真正值得看的是它怎么证明自己「输出一致」:逐字节比对加语义回归检查。
Bun 1.4.4 是 10 月 22 日发的,主角是一个内置格式化器:bun fmt。它读你现有的 Prettier 或 oxfmt 配置,兼容 Prettier 插件,然后在 Prettier 自己的仓库上跑出 59.7 毫秒——同一个仓库用 Prettier 3.9.10 是 17.1 秒。差 286 倍,内存 48 MB 对 459 MB。
数字很抓人,但我更在意另一件事:它怎么证明自己没有把代码改坏。
它覆盖了什么
一句 bun fmt src/index.ts src/styles.css 能处理:JavaScript、TypeScript、JSX、Flow、CSS、SCSS、Less、HTML、Vue、Svelte、Angular、Lightning Web Components、MJML、JSON、JSONC、JSON5、YAML、TOML、Markdown、MDX、GraphQL、Handlebars。嵌入的代码也格式化 —— HTML 里的 <style>、Markdown 里的代码块、JS 里的 css 模板字符串。
更省事的是三个常见插件被内置了:
| 原来要装 | 现在 |
|---|---|
prettier-plugin-tailwindcss |
内置,按项目里装的 tailwindcss v3/v4 排序 class |
prettier-plugin-packagejson |
内置,按同样顺序排 key,值不动 |
prettier-plugin-svelte |
内置,默认格式化 .svelte,输出对齐 4.1.1 |

速度是怎么来的
同一个仓库、同一批文件、同样的配置:
| 项目 | 文件数 | bun fmt | 对照 | 倍数 |
|---|---|---|---|---|
| Prettier 仓库 | 2,616 | 59.7 ms | 17.1 s(Prettier 3.9.10) | 286× |
| RSSHub | 7,121 | 97.3 ms | 298.5 ms(oxfmt 0.74.0) | 3.0× |
| redocly-cli | 3,397 | 62.4 ms | 217.5 ms(oxfmt) | 3.4× |
| vite | 2,537 | 65.2 ms | 406.7 ms(oxfmt) | 6.2× |
| fumadocs | 2,452 | 44.6 ms | 1,395 ms(oxfmt) | 31× |
跟 Prettier 比是数量级差距,跟 oxfmt(已经是 Rust 实现)比是 3 到 31 倍 —— 瓶颈不在「JS 还是 Rust」,而在解析器与打印器的实现方式。
对我这种站点构建来说,格式化耗时其实不痛。但它出现在每个 PR 上,还常和 lint、typecheck 串行:300 毫秒的工具能挂 pre-commit,20 秒的只能挂 CI。

「输出一致」是怎么被证明的
格式化器是个零决策工具:它不该有品味。所以换实现的唯一门槛是输出必须一致。Bun 给了三组证据:
- 和 Prettier 插件逐字节比:13,952 个 JS/TS 文件里 13,654 个(97.9%)完全相同;HTML/Vue/Angular/CSS/Markdown 等 2,176 个文件里 2,148 个(98.7%)相同;15 个 Tailwind 项目 15 个全同;13,670 个真实
package.json里 13,644 个(99.8%)逐字节相同。 - 直接跑 Prettier 自己的测试:Bun 的 CI 每个提交都跑 Prettier 3.9.9 的格式化测试,输出逐字节相同才算过。JS 1,356/1,357、JSX 186/186、TS 756/756、JSON 785/785、Markdown 1,569/1,572、YAML 1,554/1,621(95.9%,最差的一项)。
--debug-check:格式化后重新解析,如果语义变了就失败。这是 Prettier 也有的开关。
三组里我最看重第二组。「跑对手的测试套件」是兼容性最强的证据形式 —— 自己写的用例永远偏向自己的实现。
那些「故意不一样」的地方
差异里有一类特别值得看:Prettier 会改变代码含义的输入。
| 输入 | Prettier 3.9.9 | bun fmt |
|---|---|---|
CSS 里的 font-family: Font\ Awesome |
变成 Font\Awesome,\A 成了十六进制转义 |
保持原样 |
CSS 选择器 x |b |
变成 x|b,从后代选择器变成命名空间选择器 |
保持原样 |
Handlebars 里的 <!DOCTYPE html> |
丢掉 | 保留 |
Handlebars 里带 // 注释的 <script> |
合并行,注释吞掉下一行 | 保留 |
前两条都是转义被吃掉导致的语义漂移。这类 bug 在格式化器里最危险,因为 diff 看起来只是删了个空格。
装不装
我的判断:新项目直接开,老项目先在一个分支上跑一次全量 diff。理由不是速度,是那 2.1% 的差异 —— 在一个成熟仓库里,2% 的文件被重新排版会产出一次巨大的 diff,混进任何一个功能 PR 都会让 review 失效。
另外注意两个细节:
- 配置里用了 bun fmt 不支持的插件(比如
prettier-plugin-jsdoc)时,它会打印插件名、跳过受影响的文件、以退出码 2 结束。这个行为很好:宁可失败也不静默跳过。 .svelte默认会被格式化;不想动就在[fmt]里写svelte = false或加--no-svelte。

一句话
Bun 这两年一直在把工具链收进运行时 —— 打包器、测试运行器、包管理器,现在是格式化器。1.4.4 的意义不在于省了 17 秒,而在于它用逐字节比对和跑对手测试,把「可以替换」变成了可验证的结论。


评论
…