Babel 8.0.6 vs SWC 1.16.13 vs esbuild 0.28.2 per-file transform on 501 TSX files (526 KB): medians 1.05 s / 46 ms / 83 ms. esbuild bundle of same entry 40 ms. Sample Comp0.js sizes close (1089/1121/1167 B). Leave Babel if you only strip TS/JSX; keep it for macros/unported plugins.
5 min read
On a 501-file TypeScript/TSX fixture (about 526 KB of source), Babel 8.0.6 took a median 1.05 s to transform every file. SWC 1.16.13 took 46 ms. esbuild 0.28.2 took 83 ms. Same machine, same files, parallel transform API for each tool.
Short answer: leave Babel for SWC or esbuild if you only need TypeScript and JSX stripped for a bundler or loader. Stay on Babel if you depend on Babel plugins that have no SWC or esbuild port. No affiliate links in this post.
.tsx) plus one index.ts re-export file (501 files, 525,620 bytes). Classic JSX runtime, TypeScript types, no CSS.@swc/core 1.16.13, esbuild 0.28.2, @babel/core 8.0.6 with @babel/preset-typescript 8.0.1 and @babel/preset-react 8.0.1. Target ES2020, ESM output, classic React transform.Promise.all over the library APIs (swc.transform, esbuild.transform, babel.transformAsync), writing one .js per input. Median wall-clock.esbuild.build bundle of the same entry (external react) for context — median 40 ms. That is a bundler path, not the same job as per-file transform.| Tool | Role | Median | Runs (ms) |
|---|---|---|---|
| SWC 1.16.13 | per-file transform | 46 ms | 59 / 56 / 43 / 46 / 35 |
| esbuild 0.28.2 | per-file transform | 83 ms | 85 / 83 / 83 / 92 / 73 |
| Babel 8.0.6 | per-file transform | 1.05 s | 1181 / 1079 / 1038 / 1045 / 1052 |
| esbuild 0.28.2 | full bundle (extra) | 40 ms | 33 / 40 / 42 / 45 / 39 |
On this fixture SWC was about 1.8x faster than esbuild for the transform API and about 23x faster than Babel. Babel is the one that still costs a full second on 501 small files. Sample output sizes for Comp0.js were close (Babel 1,089 B, esbuild 1,121 B, SWC 1,167 B), so the decision here is time, not payload.
If your pipeline already calls esbuild as a bundler, do not rip it out because SWC won a transform-only race. Measure the job you actually run. The Rspack 2.2.8 notes are the SWC-in-bundler path on this site; the Webpack 5.111 ESM notes are the Babel-era Webpack baseline.
Babel's value is the plugin ecosystem. This fixture used only TypeScript + React presets. Real apps that rely on Babel macros, custom syntax plugins, or older transform plugins may have no drop-in SWC or esbuild option. SWC has its own plugin story (including Wasm plugins tied to a swc_core version); esbuild is intentionally small and rejects most Babel-style plugins.
Also match the transform to the bundler you keep. Vite leans on esbuild (and Rolldown work) for deps; Rspack leans on SWC. Swapping the transform library while leaving the bundler alone is fine for a loader experiment, but CI should time the full production build, not just transform(). The Vite 8.3.2 post is the closer next step if your app is Vite-first.
| Situation | My pick |
|---|---|
| Babel only strips TS/JSX for Webpack or a custom loader; no macros | Prefer SWC (or esbuild) and re-time CI |
| Already on Vite / esbuild for deps and transforms | Stay on esbuild; do not add SWC for a 40 ms micro-win |
| Moving Webpack to Rspack | Take SWC with the bundler; see the Rspack migrate path |
| Depends on Babel macros or unported plugins | Keep Babel for those files; split the pipeline if needed |
| You only care about final bundle time | Benchmark the bundler; this post measured transform APIs |
Bottom line: on 501 TSX files, Babel took 1.05 s, SWC took 46 ms, and esbuild took 83 ms for the same transform job. Who should not switch yet: teams blocked on a Babel-only plugin. Everyone else still on Babel-for-TSX should measure their own tree the same way before rewriting the loader stack.
How this was made: I generated the fixture, ran every transform on the ShopperCove test box, and kept the timing JSON; the write-up was drafted with AI help and checked against that output.
Lab evidence
Hands-on on ShopperCove box 5 Oct 2026 ~22:45-22:55 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 20.19.2). Fixture: 500 generated React TSX components + index.ts re-exports (501 files, 525620 bytes). Versions: @swc/core 1.16.13, esbuild 0.28.2, @babel/core 8.0.6 + @babel/preset-typescript 8.0.1 + @babel/preset-react 8.0.1. API: parallel Promise.all over swc.transform / esbuild.transform / babel.transformAsync; target ES2020, ESM, classic JSX. Wall medians (5 interleaved after warmup): SWC 45.883 ms (58.6/55.8/43.1/45.9/34.6); esbuild transform 83.243 ms (84.6/83.2/83.2/92.1/73.3); Babel 1052.200 ms (1181/1079/1038/1045/1052); esbuild.build bundle (external react) 40.398 ms (32.6/40.4/41.8/45.2/38.7). Sample Comp0.js sizes: Babel 1089 B, esbuild 1121 B, SWC 1167 B. Raw: /workspace/bench-xform/res-xform.json. Not tested: minify quality, source maps, decorators, Wasm plugins, Babel macros, CLI cold start, Windows/macOS, real app migrate. No affiliate.
Both cut a string at a separator, but they fail in different ways. We ran them on Python 3.13.5 to show where partition() is safer and where split() is the right tool.
Summary2 Nov 20263 min read
What changes when you swap sort() for toSorted(), with a real 1-million-item Node.js benchmark and the support table.
Summary1 Nov 20263 min read
Dragging videos is the official way to change a playlist's order. Here are the steps from YouTube Help, where to find them in YouTube Studio, and the cases where reordering will not work.
Summary31 Oct 20263 min read