写过前端的人都踩过这个坑——npm 生态的「心脏」被 Rust 换了,这件事今天被 Oxc 彻底原生化了

npm 生态的构建链路,JavaScript 写了十年。ESLint 靠 V8 跑 lint,Rollup 靠 Babel 转译,UglifyJS 靠 Node.js 压缩——每个工具各自解析一遍 AST,各装各的依赖,各占各的内存。

这事在 2026 年 9 月被彻底翻了。

Oxc 1.149 稳定了——这不是一个工具,是一整套用 Rust 重写的 JavaScript 工具链:parser、linter、transformer、minifier、formatter、codegen,全部跑在同一棵 AST 上。

什么叫同一棵 AST?

就是你写一段 TypeScript 代码,Oxc 解析一次生成 AST,然后 linter 拿着这棵树检查、transformer 拿着同棵树转译、minifier 拿着同棵树压缩、formatter 拿着同棵树排版——零次重复解析,零次重复序列化和反序列化

这件事的意义被严重低估了。


数字说话

先看几个硬数据。

Oxlint:比 ESLint 快 40 倍。不是吹的——ESLint 用 V8 解析 JavaScript,每次 lint 都要重新 parse 整个项目;Oxlint 用 Rust 的零成本抽象直接复用 AST。Shopify、Airbnb、Mercedes-Benz 已经在 CI 里把 Oxlint 作为第一道门禁。

Oxc-transform-react:8 月 18 日正式支持 React Compiler 的 Rust 原生实现。一个 1036 个文件的 React Router 项目,从 Babel 切换到 Oxc-transform-react,编译部分从 14.3 秒降到 0.81 秒——17.6 倍。整体构建时间从 22.1 秒降到 9.3 秒,2.4 倍。

pnpm 12:9 月第二周发布,Rust 重写后第一个稳定版。热安装从 472ms 压到 15ms,空闲 CPU 下降 5 倍,内存减少 35%。命令完全兼容,锁文件格式完全兼容,底层全换了。

Rolldown:Vite 8 默认引擎。Linear 生产构建从 46 秒降到 6 秒,Ramp 下降 57%,Beehiiv 下降 64%。

这四条加在一起,就是 2026 年 9 月整个 npm 生态的「心脏」被换掉了这件事的全部含义。


为什么是 Rust

V8 是为 JavaScript 运行时设计的,不是为工具链设计的。它有 GC,有 JIT 编译,有运行时开销。

Rust 没有 GC——内存分配在编译时就能确定,没有 GC 停顿。内存布局是确定的,同样的解析任务,Rust 的内存分配次数比 V8 少一到两个数量级。

再加上 Rust 的零成本抽象——你写 oxc::Parser::new() 看起来是在堆上分配,但编译器实际上把这个操作inline到了栈上,没有堆分配,没有 V8 的对象头,没有任何运行时开销。

这就是为什么 Oxc 的 minifier 能比 terser 快这么多。不是因为代码写得好,是因为语言层面就没有那些浪费。


Oxc 的架构细节

Oxc 的核心是 oxc_allocator::Allocator。所有 AST 节点都分配在一个预先分配好的内存 arena 上,而不是传统的 heap allocation。

arena allocation 的特点是:分配快,释放快,内存局部性好。所有节点都在连续的内存块上,CPU cache 命中率高。解析 10 万行的 TypeScript 文件,Oxc 的内存增长是线性的,不会出现 V8 那种因为 GC 标记导致的停顿。

具体来说,Oxc 的 pipeline 是这样的:

Source Text
    ↓
Parser (ArenaAllocator → AST)
    ↓
SemanticBuilder (带 scope、symbol、type info 的 AST)
    ↓
├── Linter (Oxlint, 43 条 type-aware 规则)
├── Transformer (JSX/TS/React Compiler)
├── Minifier (Terser 替代, 支持 ES2026)
├── Formatter (Oxfmt, Prettier 替代)
└── Codegen (source map 生成)

每一步都复用同一次 parse 结果。这就是为什么 pnpm 12 的 pnpm install 能做到 15ms 热安装——它不需要每次都重新解析 pnpm-lock.yaml


这次 1.149 的 Breaking Changes

Oxc 1.149 有几个值得注意的 breaking changes。

parser: [BREAKING] Rename panicked to fatal_error in ParserReturn。这个改动把解析失败的名字从 panicked 改成 fatal_error,更准确反映了语义——解析过程遇到 fatal error 而不是真正的 panic。这是 API 命名上的修正,对迁移影响不大但体现了规范程度。

parser: [BREAKING] Reduce MAX_LEN to 256 bytes below u32::MAX。这个是安全相关的改动,防止超大文件导致整数溢出。实际项目中极少有人真的解析 >4GB 的单文件,但这个改动体现了 Rust 在安全方面的严格态度——宁可破坏兼容性,也不留下 UB 隐患。


三步下一步

现在就能做的:

# 一行命令,用 Oxlint 扫描当前项目
npx oxlint --help

Oxlint 有 ESLint 兼容层,.eslintrc 大部分规则可以直接迁移。现有配置不需要重写,先跑起来感受速度。

这周就能做的:

如果你在用 Vite 7,可以看 Vite 8 的迁移文档。Rolldown 替代 esbuild + Rollup 是这次升级的核心变化,配置兼容度极高。Linear 的 46 秒→6 秒案例值得参考。

下个月能做的:

如果你在维护一个大型 monorepo,数一下 CI 里的 lint 时间——然后算算 40 倍加速能省多少 CI 费用。Shopify 的案例已经说明这事不是技术炫技,是工程预算的问题。


这件事的本质是:前端工具链的语言栈正在从 JavaScript 迁移到 Rust。不是渐进式变化,是整个生态在 2026 年下半年完成了心脏移植。

npm 生了三十年,Rust 用三年把它的心脏换了。

现在问题是:你还在用旧心脏吗?

评论区

0 条评论

登录后可评论。

铁锈·Rust工具链 13 阅读