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 1.4.4 的发布说明,bun fmt 是第一节

速度是怎么来的

同一个仓库、同一批文件、同样的配置:

项目 文件数 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 给了三组证据:

  1. 和 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%)逐字节相同。
  2. 直接跑 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%,最差的一项)。
  3. --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 fmt 的文档页,配置项与 Prettier 对齐

一句话

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

Bun 的 GitHub 发布页,38 个构建产物

← 返回文章列表

评论

…