从ESLint到Oxc,前端代码检查工具为何越换越快
前端工具链这几年有个很明显的规律:凡是"慢"的环节,迟早会被重写一遍。打包从 Webpack 到 Vite、Rspack,压缩从 Terser 到 SWC,格式化从 Prettier 到 Biome,现在轮到了代码检查——ESLint 的地盘。Oxc 的 oxlint 打出"比 ESLint 快几十倍"的旗号,Biome 也在同一个赛道上加速,于是问题就来了:同样一套规则,换个工具为什么能快这么多?这里到底是营销话术,还是货真价实的工程差异?
ESLint 到底慢在哪
先别急着骂 ESLint。它诞生于 2013 年,设计目标从来不是"在万行级 monorepo 上秒级跑完",而是"可扩展、可插拔、规则好写"。它的性能问题,是这套设计在今天的项目规模下被放大后的结果。
第一层是执行环境。ESLint 用 JavaScript 写,跑在 Node 上。JS 有 JIT,但 JIT 的优化是"边跑边猜",需要预热,遇到大型 AST 遍历、大量对象分配时,去优化(deopt)和 GC 停顿很难避免。加上 ESLint 是单线程的,多核机器在你这里基本是白给。
第二层是架构。ESLint 的基本流程是:读取文件 → 用 espree 解析成 ESTree 格式的 AST → 对每个启用的规则做一次遍历。注意"每个规则"——虽然 ESLint 内部会做一些 visitor 合并,但整体的对象分配量依然巨大:ESTree 是一棵纯 JS 对象树,每个节点都要独立分配、带一堆字符串键,内存占用和访问时的属性查找成本都不低。一个中等规模的仓库,几万个文件,光是建 AST、丢给 GC,就够喝一壶的。
第三层是类型感知检查。真正让 ESLint 慢到不可接受的,往往是 `@typescript-eslint` 的类型规则——它需要先构建完整的 TypeScript Program(也就是跑一遍类型检查器的语义分析),这件事本身就和 `tsc` 一样重,还得在每个文件上再叠加一次规则遍历。这也是为什么很多团队最后把类型规则单独拆成一个 CI job。
快的秘密之一:换一门语言
Oxc 和 Biome 的第一刀,都是把核心用 Rust 重写,然后通过 napi 暴露给 Node。这一步的收益非常直白:
- 没有 JIT 预热和去优化的不确定性,编译期就能把内存布局、分支预测、内联都定下来;
- 没有 GC。Oxc 的 AST 用 arena(区域分配)管理,所有节点一次性申请、一次性释放,生命周期和"解析一个文件"严格对齐,避免了海量小对象的分配与回收;
- 内存紧凑。节点用更小的结构体、索引代替指针,缓存局部性远好于散落在堆上的 JS 对象;
- 天然可并行。配合 rayon 之类的线程池,多文件可以真正同时处理,而 ESLint 要靠 `--parallel` 或者拆分包级任务来绕。
顺带一提,这也解释了为什么"Rust 重写"在解析类任务上收益特别夸张:解析和遍历是典型的 CPU 密集、内存访问不规则、对象生命周期整齐的工作负载,正好踩在 Rust 的强项上。
快的秘密之二:重新设计 AST 与遍历方式
语言只是加速器,真正拉开差距的是它们敢重新设计数据结构和算法。
- Oxc 自研的 parser 从第一行代码就是为性能写的,而不是"能产出 ESTree 就行"。它支持增量解析、错误恢复,AST 结构也是为后续遍历优化的。
- 遍历上,新工具倾向于"一次遍历,收集所有规则需要的信息",而不是按规则维度反复走树。规则从"遍历者"变成"订阅者",命中节点时才被唤醒。
- 文件读取、模块解析、路径匹配这些周边环节,也从"每个文件做一堆同步 fs 调用"改成批量、异步、可缓存的实现。
说白了,几十倍的性能差不是靠单点 trick,而是"语言 + 内存管理 + 数据结构 + 并行策略"四项同时升级的复利。
但快不是全部:生态才是护城河
如果只看跑分,ESLint 早该退役了。可现实是,绝大多数团队还在用它,原因很实在:
1. 规则生态。ESLint 积累了上千条规则和无数插件(React、Vue、Import、Jest、Tailwind……)。oxlint 虽然有兼容层,但覆盖的是高频子集,冷门规则、公司内部自研规则往往对不上。
2. 可扩展性。写一条 ESLint 规则,前端同学基本都会;写一条 Rust 规则,门槛立刻上去了。这对需要大量自定义规范的团队是硬伤。
3. 类型感知。这是最难啃的骨头,Oxc 也在专门推进类型感知能力(配合 tsgo 方向),但它天然比"纯语法规则"复杂得多。
4. 行为一致性。规则实现细节上的差异,会导致同一份代码在不同工具下报出不同结果。迁移时最怕的不是慢,是"漏报",那意味着线上事故。
实际项目里怎么选
比较务实的做法是分层,而不是一刀切替换:
- 本地开发/提交前用 oxlint 或 Biome 做快速过滤,秒级反馈,把明显问题拦住;
- CI 里保留 ESLint(尤其带类型规则的那部分)做最终关卡,同时可以用新工具先跑一遍,让慢的那一步只处理"剩下的文件";
- 新项目、规则依赖轻的项目,可以大胆直接上 Rust 系工具;
- 如果重度依赖自研插件,把它作为迁移的评估重点,而不是先看格式化规则能不能跑通。
写在最后
从 ESLint 到 Oxc,变快的原因其实不神秘:把工作从解释执行的 JS 挪到编译执行的 Rust,用 arena 替代 GC,用紧凑结构替代散落的 JS 对象,用并行替代单线程,再用更聪明的遍历替代按规则反复走树。每一层都不算颠覆性创新,但叠在一起就是量级差异。
不过工具选型从来不只是跑分题。速度决定你愿不愿意在每次保存时都跑一遍,生态决定你能不能把团队的规范真正落地。短期看,混合方案最现实;长期看,谁能把类型感知和插件生态补齐,谁才真正有资格接过 ESLint 的班。
<!--TAGS-->前端工程化,ESLint,Oxc,代码检查,Rust<!--TAGS-->
转载请注明出处,版权归原作者所有。
管理员
黑卡会员



