SWC vs Oxc: transform speed and TypeScript type-stripping fidelity
15 mins read

SWC vs Oxc: transform speed and TypeScript type-stripping fidelity

Last updated: May 16, 2026


Oxc’s headline speed advantage over SWC is real, but most of the published numbers are dev-mode numbers with sourcemaps disabled — and the moment you turn sourcemaps on for a production build, both transformers pay a tax that the headlines rarely break out. Worse, the same TypeScript file run through both transformers can emit subtly different JavaScript, and the divergences cluster in const enum, decorator metadata, and the standalone DTS-emit path. Oxc Transformer is genuinely faster than SWC on transform-heavy passes; the Oxc team attributes that lead to single-pass parsing and an arena-allocated visitor, which is also why dev-mode benchmarks flatter Oxc and why sourcemap-on production builds plausibly close some of the gap. If your code touches those fidelity-sensitive features, the headline multiplier is the wrong number to optimize for.

Overview
Editorial illustration for SWC vs Oxc: transform speed and TypeScript type-stripping fidelity head to head
Oxc’s headline speed advantage over SWC is real, but most of the published numbers are dev-mode numbers with sourcemaps disabled — and the moment you turn sourcemaps on for a production…
  • Oxc’s public benchmarks claim a meaningful transform-throughput advantage over SWC on TS+JSX fixtures, but the spread across files is wide — see oxc-project/bench-transformer for the per-file picture before assuming a single multiplier.
  • Plugin compatibility across transformer versions is a real migration cost; verify your build’s plugins against the transformer version you intend to ship.
  • Framework-bundled toolchains (Next.js, Remix, etc.) ship with their own transformer integration, so switching the underlying transformer is rarely a drop-in swap — consult each framework’s current docs.

The quick verdict: faster, mostly safe, three features to check first

For a fresh Vite + React project with vanilla TypeScript, Oxc Transformer is the right pick: faster, smaller install, and the React Fast Refresh shape Vite has aligned on. For a TypeScript codebase that uses const enum, emitDecoratorMetadata, or a strict library publishing pipeline tied to .d.ts emit, SWC is still the safer default — not because it is faster (it isn’t), but because its behavior across those edge cases has had more years of bug reports filed against it.

Topic diagram for SWC vs Oxc: transform speed and TypeScript type-stripping fidelity head to head
Purpose-built diagram for this article — SWC vs Oxc: transform speed and TypeScript type-stripping fidelity head to head.

The headline speed number is real, but it mostly tells you about the parser and the arena allocator. It does not tell you whether the JavaScript that comes out matches what tsc would have produced.

Where the speed gap actually comes from

The two compilers differ at the architectural level. SWC, per the project’s own documentation, uses a multi-pass visitor pipeline; each transform pass walks the AST. The cost is real, but so is the flexibility that pipeline buys.

Oxc, per the project site, was designed around a single-pass arena-allocated visitor written in Rust. Memory is allocated in bump arenas and freed in bulk at the end of the run, the AST is visited once, and transforms are composed at compile time.

Background on this in ESM live bindings.

Benchmark: SWC vs Oxc transform throughput
Measured: SWC vs Oxc transform throughput.

The benchmark image above shows the throughput gap on TS + JSX fixtures. The benchmark repo’s own results show the spread: small idiomatic TS + JSX files hit the upper bound of the reported range, while larger real-world fixtures sit meaningfully below it. The Oxc team’s own framing is that single-pass parsing and the arena-allocated visitor account for most of the lead, and that SWC’s printer is generally regarded as fast — so the bulk of the published delta tracks parsing and visiting rather than codegen. Treat that as the project’s attribution rather than a measured parse-vs-visit-vs-codegen profile; I have not seen a head-to-head breakdown that isolates the three phases on identical fixtures.

The fidelity matrix: same TS in, different JS out

Speed gets the headlines. Fidelity is what survives a release. The features below are the ones to verify on your own code before switching transformers, because each one has a meaningful semantic component that goes beyond stripping types.

TypeScript feature support to verify before switching — check each tool’s current docs for status.
TS feature What can diverge Why it matters at runtime
const enum Inlined integer literal vs preserved object lookup vs refusal to emit when isolatedModules is on. Inlining is faster and dead-code-eliminable; preserved enums show up at runtime and break tree-shaking.
Parameter properties Constructor assignment ordering relative to super() calls and field initializers. Side-effect ordering in constructors of class hierarchies; subtle for React class components and decorators.
Type-only import elision Whether import { T } from './x' (T used only as a type) is removed from emitted JS. Failure to elide pulls modules into the runtime graph and can cause circular-import warnings.
emitDecoratorMetadata Whether design:type, design:paramtypes, and design:returntype are emitted via Reflect.metadata. DI containers and reflection frameworks (NestJS, Angular, TypeORM, class-validator) call Reflect.getMetadata at runtime to resolve constructor parameter types and column shapes; missing or mismatched metadata silently misresolves dependencies and stops validators from matching types.
useDefineForClassFields Class fields emitted as Object.defineProperty vs this.x = … assignment. The difference is observable when a base class declares an accessor for the same name; ES2022 spec semantics depend on this.
Namespace merging / import = Whether legacy namespace blocks and import x = require('y') are accepted, partially supported, or rejected. Mostly hits older codebases and ambient .d.ts files; if you maintain a library that ships namespaces, this is load-bearing.
Comparison: SWC vs Oxc
Feature comparison — SWC vs Oxc.

The side-by-side above is the practical takeaway in one image: identical TypeScript input can yield two outputs whose diff is small but whose runtime behavior is meaningfully different.

For more on this, see type-safety fundamentals.

It deliberately lists categories rather than asserting one-to-one rule mappings, because both projects ship rapid fixes and a divergence today often closes in two releases.

The sourcemap tax and why Oxc’s lead probably narrows when you turn maps on

Every speed benchmark you read has a sourcemap axis it rarely interprets. Map generation is not free for either transformer: VLQ-encoding mappings, building name tables, and base64-encoding the inline map all cost real CPU. Both projects expose source-map toggles in their respective configs (sourceMaps for SWC, sourcemap for Oxc’s transform options), and on identical inputs both pay a measurable percentage tax when maps are on.

The pattern to expect, with the caveat that few published benchmarks report sourcemap-on numbers next to sourcemap-off numbers on identical fixtures: Oxc’s transform-pass advantage is most visible when sourcemaps are off, because parsing and visiting dominate wall-clock. When sourcemaps are on, codegen and map emission take a larger share of the total, and codegen is the part of the pipeline where SWC is already competitive. Without a head-to-head sourcemap-on benchmark to point at, the size of any narrowing is an inference rather than a measurement — but the practical implication holds either way: production builds (where you keep maps on for error tracking) are likely to see a smaller relative speedup than dev builds (where you often disable them). Treat any benchmark headline number as a dev-mode number unless the methodology says otherwise, and if the size of the gap matters to your team’s decision, run a sourcemap-on pass on your own fixtures before committing.

More detail in prefetch latency tradeoffs.

Isolated declarations is not a free win — it’s a win on code that already complies

Oxc’s standalone DTS emit is one of the most striking numbers in the entire benchmark set.

The motivation is that with explicit annotations, a declaration file can be produced from a single source file without performing full whole-program type inference — which is the work that makes tsc slow. Run a standalone DTS emitter on a non-compliant file and you get a diagnostic, not a degraded declaration.

There is a longer treatment in writing async TS that compiles cleanly.

Terminal output for SWC vs Oxc: transform speed and TypeScript type-stripping fidelity head to head

Real output from a sandboxed container.

The terminal capture above is what failure actually looks like: a clear error pointing at an exported function whose return type was inferred. The fix is to add the annotation — which is good engineering, but it is real refactor work. For an existing library with thousands of public exports, “switch to Oxc for faster DTS emit” reads as a one-day swap and is in fact a multi-week annotation campaign.

Plugin ABI is the real lock-in

The migration cost the SERP hand-waves past is plugin compatibility. Whichever transformer you adopt, the plugin contract you depend on is the part most likely to bite you on an upgrade — verify each plugin in your build against the exact transformer version you intend to ship, rather than assuming forward compatibility.

Official documentation for swc vs oxc transformer

Official documentation.

Background on this in Storybook 9 migration notes.

Oxc Transformer’s design pushes back on a plugin surface for an architectural reason: the single-pass arena visitor that makes it fast would have to serialize AST nodes across an FFI boundary to host external plugins, which is exactly the cost the design avoids. The Oxc team has said publicly that a plugin surface is possible but is not the current priority.

A project-profile decision rubric

Match the row to your codebase.

Transformer choice by project profile, 2026.
Profile Pick Reason
Next.js application SWC Stay on the transformer the framework ships with unless its docs explicitly support swapping it out.
Vite + React app, no SWC plugins Oxc The Oxc-based React plugin is the direction Vite’s Rolldown-powered roadmap is heading; you give up nothing.
Vite + React app using a SWC plugin (e.g. styling, i18n) SWC Keeping the transformer your plugins are built against preserves the version matrix you depend on.
Bazel / Buck monorepo with custom transforms SWC If you have already written a custom plugin against SWC, rewriting it against a different visitor model is not a free swap.
Library publishing .d.ts for public consumers Oxc, after annotating exports The DTS-emit win is real once your code passes the isolated-declarations rules.
NestJS / TypeORM / Angular service SWC, verify metadata emit Your DI container depends on emitDecoratorMetadata; verify both transformers, but SWC has more years of bug reports against this surface.
Plain tsc CI replacement, no framework Oxc Best speed and the smallest install; fidelity edges are easy to verify when there is no framework abstracting your build.

What to watch in the next two releases

A few things will move the needle. Target lowering is the first: Oxc Transformer has been focused on stripping TS and JSX rather than lowering to older ES versions (down-leveling async/await to a state machine, for example). SWC already does both. If you target ES2015 in production, that gap is the deciding factor today. The CJS module transform is the second: production builds that ship CommonJS for legacy Node consumers depend on module.exports wiring that Oxc has historically left to a bundler. The third is an eventual plugin surface for Oxc — even a narrow one, scoped to AST visitors compiled to native code, would change the lock-in calculation that currently keeps complex applications on SWC.

If you need more context, structuredClone vs JSON tradeoffs covers the same ground.

For more on this, see modern JS techniques worth knowing.

The single best test of “should I switch” is not a benchmark. It is taking your repo’s hardest TypeScript file — the one with the namespace, the decorator, and the const enum — running it through both transformers, and diffing the output against tsc‘s. If the diff is empty, the speed number is the only thing that matters. If the diff is not empty, you have just discovered which transformer you are actually shipping to production.

Frequently asked questions

Is Oxc actually faster than SWC for TypeScript transforms?

Yes, Oxc Transformer is genuinely faster than SWC on transform-heavy passes, with the lead coming from single-pass parsing and an arena-allocated visitor. The reported multiplier is usually a dev-mode number with sourcemaps disabled, however. Turn sourcemaps on for a production build and codegen plus map emission take a larger share of wall-clock, which is the part of the pipeline where SWC is already competitive.

Does Oxc handle const enum and emitDecoratorMetadata the same way as SWC?

Not reliably. const enum behavior diverges in three directions across transformers: inlined integer literal, preserved object lookup, or refusal to emit under isolatedModules. For emitDecoratorMetadata, both projects emit Reflect.metadata calls, but the precise design:type and design:paramtypes shapes have historically differed. If your DI container or ORM resolves types at runtime, verify on your own code before switching.

Can I keep my existing SWC plugins if I migrate to Oxc?

No. Oxc Transformer does not currently expose a plugin ABI compatible with SWC’s, and its single-pass arena visitor is architecturally hostile to FFI-based plugin hosts. Any custom transform written against SWC’s visitor — styling injection, i18n string extraction, or codemods — would need a rewrite against whatever surface Oxc eventually ships. The Oxc team has said publicly that a plugin surface is possible but not the current priority.

Further reading

Leave a Reply

Your email address will not be published. Required fields are marked *