用 Biome 替代 ESLint 和 Prettier:速度与你仍需遵守的 Hooks 规则
Biome与ESLint及Prettier的测试时间对比、react-hooks与exhaustive-deps之间的差异,以及何时在双运行环境下使用一个ESLint插件才是合理的迁移方式。
营销内容宣称该工具能提供Rust二进制文件、单一配置,以及20到50倍的效率提升。之后进行了四路测试,同时统计了Biome无法处理的诊断问题。
使用单个二进制文件与多插件组合相比,处理时间有所减少,但对某条重要规则的覆盖率却没有提升。
查看package.json文件。
统计与代码检查相关的依赖项。那些仍包含eslint、prettier、eslint-config-prettier、eslint-plugin-react-hooks以及解析插件在内的项目,每次提交都会产生额外的开销。Biome则宣传自己是一个可同时完成格式化、代码检查及导入排序功能的单一执行文件。
在该实验室的现有ESLint工具链旁新增了Biome。两种流程都经过了时间测试,之后便移除了ESLint。某个依赖的诊断工具没有看似安全的Biome对应版本。流程最终通过测试,而该诊断工具此前用于阻塞的错误类别在特性分支上又出现了。
当主题切换时,该分支会重新连接WebSocket——这是典型的效果依赖问题。而Biome则保持沉默。
Biome实际输出的句子
官方称,Biome的格式化结果与Prettier的匹配度约为97%,其代码检查规则集则借鉴了ESLint和typescript-eslint。相关设置保存在biome.json文件中,日常使用方式类似pnpm exec biome check --write。
那些炫目的文章没有提及:一些冷门插件及内部规则可能仍然缺失。对于这些不足,要么继续使用ESLint,要么将相关逻辑移植过来。
速度很容易让人喜爱。而覆盖率的缺失则如同租金一般难以承受。
实际记录的时间
实验室使用了TypeScript、React以及少量服务器模块——大约有六十个非node_modules文件。Instant Navigations未被启用,因此仅测量了检查工具的运行时间。
pnpm exec eslint . --max-warnings=0
pnpm exec prettier --check .
pnpm exec biome check .
每种工具各运行三次,中值如下:
- 仅使用ESLint:4.1秒
- Prettier的
--check选项:1.6秒 - 先使用ESLint再使用Prettier:5.7秒
- Biome的
check选项:0.22秒
远未达到50倍的差距。与顺序运行的两种工具相比,在小型项目中的加速倍数约为26倍。博客文章中通常提到的文件数量为1万左右。此测试使用的是实际部署的应用程序,趋势数据与之相符,而那些夸大的倍数则不然。
在两次检查中,Prettier与Biome对两个文件存在分歧——一个是位于InvoiceRow内部的冗长JSX属性嵌套结构。Biome保留了原有的嵌套格式。97%的匹配率是真实的;剩下的3%只有在团队逐个文件争论时才会引发争议。
如何在您的设备上查看结果
pnpm add -D --save-exact @biomejs/biome
pnpm exec biome init
会出现一个biome.json文件。在ESLint仍存在的状态下运行一次Biome。在得到一份记录了ESLint发现但Biome未报告的所有问题的清单之前,不要删除任何内容。
pnpm exec eslint . -f unix > /tmp/eslint.txt
pnpm exec biome check --reporter=json > /tmp/biome.json
手动对比后发现,大多数recommended级别的问题都是重叠的。唯一存在差异的问题是:
react-hooks/exhaustive-deps
Biome包含了与钩子相关的检查项,但这些检查与之前针对socket效应设置的警告并不完全一致。在CI系统移除了那条规则后,主题切换相关的连接问题又漏掉了。
ESLint仅针对一个插件返回了警告:
{
"scripts": {
"lint": "biome check . && eslint app --plugin react-hooks --rule 'react-hooks/exhaustive-deps:error'"
}
}
操作繁琐且不够透明。当缺失的规则有名称时,同时使用两个工具是现代环境中一个合理的折中方案。但为了“以防万一”而保留完整的ESLint配置树通常只是浪费资源。
应在当天晚上就验证编辑器的相关功能。Biome的语言服务器取代了原本使用的两个扩展插件,结果钩子相关的警告只出现在持续集成环境中——如果在本地不继续保留针对该规则的ESLint扩展,情况会更糟。最好能在/settings中看到相关提示,而非在早晨的构建过程中才发现问题。
究竟哪种方式更快
Biome以单个原生进程的方式遍历配置树。而ESLint则是基于Node加上各种插件,之后还可能用到Prettier。在60个文件的情况下,加载插件就会占用4.1秒中的大部分时间;一旦文件数量达到数千个,遍历配置树的操作就会占据主导地位,此时就会出现类似大规模项目中的性能表现。
Biome 2 提供了基于类型的代码检查功能,但并未实现 TypeScript-eslint 的全部功能。如果持续集成仍然支持 parserOptions.project,则应单独衡量该流程的耗时——因为那正是性能开销最大的 ESLint 模式。与旧版的 no-unsafe-* 设置相比,其功能存在差距。
关闭基于项目的解析功能后,CI 流程的耗时从约 90 秒降至约 8 秒,却将这一提升归功于 Biome,其实只是因为去掉了基于类型的检查功能而已。遇到这种情况时务必说出来。
详细费用清单
流水线处理时间:从 5.7 秒降至 0.22 秒。多模块项目能最明显地感受到性能提升,而小型应用则主要体现在笔记本电脑上的运行速度更快。
规则方面:有一个依赖检查项消失了,但该路径仍保留 ESLint 检查。
格式化相关问题:有两个 JSX 包装结构,但可以解决。
编辑器方面:两个扩展合并为一个,后又恢复为两个——整体差异不大,但命令行检查速度更快了。
注意:绿色 Biome 运行结果并不等同于绿色 hooks 树。拉取请求中应注明是哪位运行者标记了该文件。
保持启用状态,或停止夜间任务
新项目仓库今晚即可采用 Biome 进行格式检查及基础代码规范校验,完全无需使用 Prettier。
旧项目应进行约一周的双重运行测试。仅对有名称的插件保留 ESLint,删除未使用的配置文件,而不仅仅是对应的 CI 步骤。
当自定义规则本身就是产品功能时,仍需继续使用 ESLint,并将这些规则记录下来。“也许我们需要插件”并不能算作需求清单。
绝不能仅仅因为 Biome 运行速度快就放弃使用 exhaustive-deps。主题切换场景就能证明该规则存在的必要性。
值得明确说明的注意事项
0.22秒对比5.7秒的数据是在一台笔记本电脑上对60个文件进行的测试结果——并非针对1万份文件的测试,也不是56倍的性能提升。
钩子间距取决于配置以及当夜的生物群系构建版本。在将该问题视为永久性故障之前,请重新运行 biome rage 并查看当前的钩子诊断信息;不同版本中标识符可能会发生变化。
插件的管理方式各不相同。如果仅通过 eslint-plugin-import 来控制导入顺序,可尝试使用 Biome 的 organize-imports 功能一周时间。
同步运行这两个工具,列出在清理 Biome 后仍然存在的 ESLint 错误。
这份清单本身就是迁移工作的一部分。剩余的问题需要通过明确的职责划分,由专门的脚本来处理。
当 CI 检测通过且套接字重新连接后
原本使用的 ESLint 加 Prettier 已被 Biome 取代。检查60个文件的耗时从5.7秒降至0.22秒。某个分支已被合并,切换主题后又重新建立了总线的 WebSocket 连接。react-hooks/exhaustive-deps 之前会阻止这种模式,而 Biome 则不会产生相关的错误提示。
糟糕的响应。应暂停该插件,直到“迁移完成”。否则客户将无法看到实时数据。
更好的响应方式。Biome负责格式处理及基础代码检查,而ESLint仅保留用于app目录下的某个插件。
{
"scripts": {
"lint": "biome check . && eslint app --plugin react-hooks --rule 'react-hooks/exhaustive-deps:error'"
}
}
当缺失的规则被明确指出时,两种处理方式都是合理的;但若整个ESLint配置都被保留,则属于资源浪费。格式差异表现为:JSX需要多一层包裹,而Biome版本则保持不变。
请在自己的项目中进行测试。在清理完Biome后的配置中,列出仅由ESLint处理的规则。先列出规则,再记录测试时间。
实验环节的命令与输出
实验前的版本为:Node 24、TypeScript 7、Next 16.3。请将这些信息记录在实验笔记中,以便后续实验无需猜测。
node -v
pnpm exec tsc -v
pnpm exec next --version
请将这三行内容放在笔记的最上方。如果主要版本与所遵循的指南不一致,应立即停止——否则后续命令可能会悄悄导致错误结果。
接下来的路线演示:
pnpm exec next dev
输入 /、/invoices、/invoices/1、/settings,然后再输入一次 /invoices。保持 DevTools 的日志记录功能开启。截取筛选界面和对应的 URL,这对后续相关实验非常有用。
类型检查:
pnpm exec tsc --noEmit --pretty false
echo $?
退出码为零并不代表产品已正式发布,它仅表示为运行时检查清除了路径。
之后,重新执行“如何在您的机器上查看”章节中的检测命令。不要因为上面有数字就跳过这些步骤——不同的机器、环境温度或繁忙的 Chrome 标签页,都会比框架补丁更显著地影响内存使用、检测时长以及计时结果。
记录一条简短的故障处理记录,格式为:“尝试了 X,但仍然出现 Y。”版本信息、命令内容、输出结果以及那句话都是很有用的后续参考资料,而营销截图则没有这种价值。
故障记录本
在 Biome 到达的同一天移除 ESLint 后,套接字问题又出现了。恢复其中一个插件后,代码覆盖率才得以恢复。
试图在 Biome 2 中复制 project-aware typescript-eslint 的行为时,只发现了部分相似之处。旧版的 no-unsafe-* 规则组并不匹配,强行使其匹配会导致问题。
一场持续很久的 JSX 属性格式冲突最终通过采用 Biome 得以解决。
在编辑器中,Biome 的 LSP 取代了 Prettier 和 ESLint 扩展。钩子相关的警告仅出现在 CI 环境中,因此该规则仍保留 ESLint 扩展。两个扩展仍然比五个更好。
删除 ESLint 之前的检查清单
- [ ] 执行
biome check后结果正常 - [ ] 已记录所有仅由 ESLint 处理的规则
- [ ] 已决定是保留还是主动放弃某些命名插件
- [ ]
exhaustive-deps已有可信赖的替代方案,否则仍保留 ESLint
0.22秒对5.7秒,相差六十倍。请在本地重新测量,并在将这一差距视为永久性结果之前,再次检查已安装的Biome中的钩子诊断功能。
发票应用中的生产环境说明
CI节省的是实际时钟时间。在演示过程中,更换主题导致websocket重新连接,因为钩子覆盖率消失了。重要的不是时间数值,而是实时的汇总数据。
与其使用速度极快却会漏检重新连接的检查工具,不如选择速度稍慢但能捕获这些情况的工具。将0.22秒的Biome与一个ESLint插件结合使用也是不错的选择。新项目可以直接从Biome开始,等有规则需要时再添加ESLint;老项目则无需进行不必要的纯化操作。
命令
pnpm exec eslint . --max-warnings=0
pnpm exec prettier --check .
pnpm exec biome check .
运行三次以获取数据,然后计算中位数。记录 ESLint、Prettier 以及 Biome 的运行结果。输出 ESLint 的 Unix 格式结果,并将 Biome 未报告的错误项进行标注。将这些标注放入拉取请求中,可将时间信息视为脚注处理。
真正有意义的差异在于那些没有对应规则的错误项。
基于真实项目的练习
选择已发布的实际项目仓库,而非沙箱环境。分别记录 eslint .、prettier --check . 和 biome check . 的执行时间——每种命令运行三次。将中位数与文件数量一同列在表格中。
可以先手动生成规则差异对比,因为自动重命名功能常常会带来误导。列出在 Biome 处理完成后仍然存在的所有 ESLint 错误项。如果列表为空,则说明这些错误可能是 ESLint 本身留下的;如果列表中包含 react-hooks/exhaustive-deps 或对产品至关重要的自定义插件相关错误,则需保留这些错误项。
打开一个包含已知真实缺失依赖项的钩子的分支,看看是哪个工具会报错。正是由于这个分支的存在,比较过程才不仅仅是竞态条件问题。
将表格和规则列表提交上去。避免使用仅会删除包的“切换到Biome”类型拉取请求,删除操作应是最后一步,而非起始动作。