ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 33

  1. Blog

magicast vs recast: 0.65 ms vs 0.42 ms per Vite Edit

Aditya Challa·5 October 2026·8 min read

Hands-on
On this page
  1. What I tested
  2. How fast is magicast vs recast?
  3. Which one starts faster?
  4. What does each one cost to install and bundle?
  5. Do they write the same output?
  6. What breaks when the config is unusual?
  7. Which one should you pick?
  8. Sources
  9. Related

I timed magicast 0.5.5 against recast 0.24.0 on one codemod job, adding a Vite plugin import plus a plugins entry and changing server.port in a 278 B vite.config.ts: 0.65 ms vs 0.42 ms per edit in run 1 and 0.55 ms vs 0.41 ms in run 2 (1.35× to 1.53×). The magicast version took 4 lines of code against 10 for hand-written recast, and on a 3,627 B config the two were within 10% of each other.

Short answer: pick magicast when you edit config files (vite.config, nuxt.config) from a CLI or setup script and want a proxy API plus ready helpers like addVitePlugin. Pick recast when you need full control over where nodes land, want byte-identical round trips, or are writing a general codemod. magicast ships a vendored copy of recast inside its own dist, so this is a choice of API level, not of engine. No affiliate links in this post.

What I tested

  • Machine: 8 vCPU Intel Xeon / 15 GB RAM shared Linux cloud box, tested 6 Oct 2026 about 04:10 IST.
  • Versions: magicast 0.5.5 and recast 0.24.0 (both npm latest that day), @babel/parser 7.29.9 for recast's babel-ts parser, Node 20.19.2, esbuild 0.28.2 for bundle sizes.
  • Workload: three fixtures. A 278 B vite.config.ts with comments and no semicolons, a 3,627 B nuxt-style config with 8 modules, 24 runtime flags and 20 route rules, and an 18,316 B route.tsx from TanStack Router.
  • Jobs: parse only; config edit (add import Inspect from 'vite-plugin-inspect', push Inspect() into plugins, set server.port to 3000, then print); and on route.tsx, add one import and print. magicast used parseModule, addVitePlugin, getDefaultExportOptions and generateCode. recast used recast.parse with recast/parsers/babel-ts, ast-types builders and recast.print with single quotes.
  • Timing: median of 9 rounds (2,000 / 300 / 60 iterations by size), two processes with the run order flipped. Scripts, outputs and JSON in /workspace/bench-magicast-recast/.
  • Also measured: cold import and first edit (11 fresh processes), esbuild bundle size, fresh npm install size, line diffs of the output, round-trip fidelity, and rerun safety.
  • Not tested: magicast loadFile / writeFile on disk, its parseExpression and builders beyond the helper, recast with esprima or flow parsers, jscodeshift, Bun and Deno, and configs exported through a variable.

How fast is magicast vs recast?

Job (median ms, run 1 / run 2)magicast 0.5.5recast 0.24.0
Parse vite.config.ts, 278 B0.169 / 0.1510.215 / 0.127
Edit + print vite.config.ts0.649 / 0.5500.424 / 0.406
Parse nuxt.config.ts, 3,627 B1.857 / 1.8251.689 / 1.652
Edit + print nuxt.config.ts4.426 / 4.5174.118 / 4.487
Parse route.tsx, 18,316 B8.503 / 7.3927.060 / 7.114
Add import + print route.tsx16.79 / 18.0515.91 / 16.70

Parsing was close because both run @babel/parser underneath. If you only need to read exports, not rewrite them, see mlly vs acorn. The gap on the small config came from magicast's proxy layer and helper work, and it shrank to 1% to 8% on the larger config and to 5% to 8% on route.tsx. For a setup CLI that edits one config once, both finished well under a millisecond on the Vite file. If you are choosing the CLI framework around that edit, my citty vs commander run covers the startup side.

Which one starts faster?

Cold start (median of 11 processes, Node 20.19.2)magicastrecast + babel-ts
Import only74.76 ms60.45 ms
Import + first vite.config.ts edit99.34 ms84.94 ms

recast started about 14 ms sooner in a fresh process. In a one-shot npx scaffolder that is the number users feel more than the per-edit time. For CLIs that also detect the user's package manager before writing files, see pm-detector vs which-pm.

What does each one cost to install and bundle?

Entryesbuild minifiedgzip -9Input filesFresh npm install
magicast (parseModule, generateCode, two helpers)500,529 B127,685 B66,012 KB, 6 packages
recast + recast/parsers/babel-ts657,135 B160,904 B618,200 KB, 10 packages (with @babel/parser)
recast alone (default esprima parser)———2,800 KB, 6 packages

magicast came out smaller once recast had a TypeScript-capable parser installed, because magicast vendors its own copy of recast and pulls only @babel/parser, @babel/types and source-map-js. The recast bundle also kept esprima, its default parser, even though the babel-ts parser was used. If you ship either inside a CLI, tsup vs unbuild covers the build step.

Do they write the same output?

Check (vite.config.ts edit)magicast 0.5.5recast 0.24.0
Lines added / removed+3 / −5 (16 to 14 lines)+4 / −4 (16 to 16 lines)
Where the new import landedline 1, above the header commentline 5, after the last import, with a blank line
Multi-line plugins arraycollapsed to plugins: [vue(), Inspect()]collapsed to plugins: [vue(), Inspect()]
// comments keptyes (2 of 2)yes (2 of 2)
Quote stylesingle, matched the filesingle (quote: 'single' set by me)
New import lineends with a semicolon in a no-semicolon fileends with a semicolon in a no-semicolon file
Trailing newlinedroppedkept
Parse + print with no editsidentical except the final newlinebyte-identical on all 3 fixtures
Code I wrote for the edit4 lines10 lines

Both left a diff that a formatter would need to clean up: the collapsed array and the stray semicolon. I did not run a formatter in this bench; my dprint vs Prettier comparison covers that step. When the edit is merging plain objects rather than rewriting source, defu vs lodash.merge is the lighter path.

What breaks when the config is unusual?

Casemagicast 0.5.5recast (my hand-written edit)
defineConfig(({ mode }) => ({ ... })) function configaddVitePlugin returned true but only added the import; plugins unchanged; server.port line threw TypeErrorthrew TypeError (my code expected an object)
Run addVitePlugin twice on one modulethrew MagicastError: Changing import name is not yet implementedn/a
Run the edit again on its own outputthrew the same MagicastErrorwrote a second import and a second Inspect()
Plugin options objectaddVitePlugin options produced Inspect({ ... })build the object node yourself

The function-config case is the one to watch: magicast reported success while the plugin never reached the plugins array. Check the generated code for the plugin call before you write files. If your setup tool also reads env or rc files next to the config, confbox vs dotenv covers that layer.

Which one should you pick?

SituationMy pick
Add a plugin or module to vite.config / nuxt.config from a CLImagicast
Precise control of insert position and formattingrecast
Byte-identical round trip on untouched filesrecast
Shortest code for common config editsmagicast (4 vs 10 lines here)
One-shot scaffolder where startup mattersrecast (about 14 ms faster cold)
TypeScript parser bundled without extra installsmagicast (6,012 vs 8,200 KB)
General AST codemods across many filesrecast, or a tool built on it

Bottom line: on this box magicast 0.5.5 took about 0.55 to 0.65 ms for a Vite config edit vs recast 0.24.0 at about 0.41 to 0.42 ms, with near-equal times on larger files, and it needed 4 lines of code instead of 10. Who should not switch to raw recast: setup CLIs that only touch standard config shapes. Who should not rely on magicast helpers alone: tools that must handle function configs or rerun safely, since I saw a silent partial edit and a thrown MagicastError on rerun. Type-checking after the edit is a separate step; see vite-plugin-checker vs tsc.

How this was made: I installed both packages on the ShopperCove box, timed parse, edit and print across three fixtures over 9 rounds in two processes, checked cold start in 11 fresh processes, bundled each entry with esbuild, ran fresh npm installs for size, and diffed every output file against its source. The JSON results and generated files sit next to the scripts. The write-up was drafted with AI help and checked against that output.

Sources

  • https://github.com/unjs/magicast
  • https://github.com/benjamn/recast
  • https://www.npmjs.com/package/magicast
  • https://www.npmjs.com/package/recast
  • https://babeljs.io/docs/babel-parser
  • https://esbuild.github.io/

Related

  • https://www.shoppercove.com/blog/mlly-vs-acorn
  • https://www.shoppercove.com/blog/citty-vs-commander
  • https://www.shoppercove.com/blog/pm-detector-vs-which-pm
  • https://www.shoppercove.com/blog/tsup-vs-unbuild
  • https://www.shoppercove.com/blog/dprint-vs-prettier
  • https://www.shoppercove.com/blog/defu-vs-lodash-merge
  • https://www.shoppercove.com/blog/confbox-vs-dotenv
  • https://www.shoppercove.com/blog/vite-plugin-checker-vs-tsc
magicastrecastcodemodviteconfigperformancejavascript

Lab evidence

What I found running this

Hands-on on ShopperCove box 6 Oct 2026 ~04:10 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 20.19.2). magicast 0.5.5 vs recast 0.24.0 + recast/parsers/babel-ts with @babel/parser 7.29.9; esbuild 0.28.2. Fixtures 278 B vite.config.ts, 3,627 B nuxt-style, 18,316 B route.tsx. Median of 9 x 2,000/300/60, two processes: Vite edit 0.649/0.550 vs 0.424/0.406; nuxt 4.426/4.517 vs 4.118/4.487; route import 16.79/18.05 vs 15.91/16.70; parse within ~27%. Cold import 74.76 vs 60.45 ms, first edit 99.34 vs 84.94. Bundle gzip 127,685 vs 160,904 B (recast kept esprima); install 6,012 vs 8,200 KB. Output: magicast +3/-5 lines, import at line 1, newline dropped; recast +4/-4, byte-identical round trip. Function config: addVitePlugin returned true, plugins unchanged. Rerun: MagicastError. Code 4 vs 10 lines. Not tested: loadFile/writeFile, jscodeshift, Bun/Deno. No affiliate.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Related links

  • Plate 06

    oxc-parser vs @babel/parser: 9.1 ms vs 47.9 ms on 513 KB

    5 Oct 2026

  • Plate 79

    pm-detector vs which-pm: 0.49 ms vs 0.043 ms

    5 Oct 2026

  • Plate 93

    mlly vs acorn: 0.046 ms vs 0.017 ms findExports

    5 Oct 2026

On this page

  1. What I tested
  2. How fast is magicast vs recast?
  3. Which one starts faster?
  4. What does each one cost to install and bundle?
  5. Do they write the same output?
  6. What breaks when the config is unusual?
  7. Which one should you pick?
  8. Sources
  9. Related
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove