ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 93

  1. Blog

mlly vs acorn: 0.046 ms vs 0.017 ms findExports

Aditya Challa·5 October 2026·6 min read

Hands-on
On this page
  1. What I tested
  2. How fast is mlly findExports vs acorn.parse?
  3. What does each one cost to install and bundle?
  4. Do they behave the same?
  5. Which one should you pick?
  6. Sources
  7. Related

I timed mlly 1.8.2 findExports against acorn 8.19.0 parse on a 240 B ESM fixture on this box: 0.046 ms vs 0.017 ms per call. That is about 2.7× in acorn's favor on raw parse, while mlly's full esbuild node minify+gzip landed at 42,531 B against acorn's 34,173 B.

Short answer: pick mlly when you need ESM utilities (findExports, findStaticImports, detectSyntax, resolvePath) without writing an AST walk. Pick acorn when you need a full Program AST, custom walkers, or the fastest parse of the same source. mlly depends on acorn and uses its tokenizer on some paths, so this is a layering choice, not a drop-in fork. 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 03:50 IST.
  • Versions: mlly 1.8.2, acorn 8.19.0 (both npm latest that day), Node 20.19.2, esbuild 0.28.2 node ESM minify + gzip -9.
  • Workload: three fixtures (240 B / 2,720 B / 34,608 B). Timed mlly findExports, findStaticImports, findExportNames, detectSyntax, resolvePathSync vs acorn.parse, a hand-rolled export-name walk on the AST, and acorn.tokenizer drain. Median of 9 rounds (5,000 / 2,000 / 500 iters by size), two order-stable processes. Scripts and JSON in /workspace/bench-mlly-acorn/.
  • Also measured: esbuild bundle sizes for mlly (named exports) vs findExports-only vs acorn parse+tokenizer, and alone-on-disk install size.
  • Not tested: TypeScript/JSX plugins, acorn-walk / estree transformers, magicast/recast codemods, Bun/Deno hosts, and adversarial malformed sources beyond what detectSyntax classifies.

How fast is mlly findExports vs acorn.parse?

Job (median of 9, ms)Run 1Run 2
mlly findExports, 240 B0.04560.0459
acorn.parse, 240 B0.01760.0173
acorn export-name walk, 240 B0.01660.0171
mlly findStaticImports, 240 B0.01980.0203
mlly detectSyntax, 240 B0.000420.00041
mlly findExports, 2,720 B0.3440.346
acorn.parse, 2,720 B0.1900.189
mlly findExports, 34,608 B11.1710.89
acorn.parse, 34,608 B10.3310.20
acorn.tokenizer drain, 240 B0.00990.0101
mlly resolvePathSync0.01650.0169

On the small ESM file acorn.parse was about 2.7× faster than mlly findExports. On the 34 KB synthetic module they were nearly tied (about 11.0 vs 10.3 ms). detectSyntax stayed under 0.0005 ms because it is a regex scan, not a full parse. For loader/register paths next to module analysis, see esbuild-register vs jiti; for compiler/parser stacks, swc vs esbuild/babel.

What does each one cost to install and bundle?

Package / entryMinifiedgzipInput filesAlone on disk
mlly (findExports + imports + detectSyntax + resolvePathSync)144,451 B42,531 B131,572 KB (mlly + acorn + pathe + pkg-types + ufo)
mlly findExports only130,782 B37,764 B13—
acorn (parse + tokenizer)121,656 B34,173 B2604 KB (1 package)

mlly's install pulls its dependency tree; acorn alone is about 2.6× smaller on disk here. Bundle gzip still favors acorn by about 8 KB for the entries above. That gap matters the same way size locks do in size-limit vs bundlesize and when you ship analyzers via tsup vs unbuild.

Do they behave the same?

Checkmlly 1.8.2acorn 8.19.0
Export names on small fixtureVERSION, greet, pathUtil, default, AppVERSION, greet, default, pathUtil
findExports detailtyped rows (declaration / named / default) with start/endfull Program AST; you walk Export* nodes
Static importsfindStaticImports → node:fs/promises, patheImportDeclaration nodes on the AST
detectSyntax ESM-onlyhasESM true, hasCJS falseN/A (parse with sourceType module)
detectSyntax CJS-onlyhasCJS trueparse as script/module separately
Mixed import + module.exportsisMixed truevalid only if you choose sourceType carefully
Path resolveresolvePathSync to absolute file URL/pathnot in scope (parser only)
Dependencydepends on acorn (^8.16), uses tokenizerzero runtime deps

mlly's findExportNames listed App from export default class App while the simple AST walk only recorded default — a reminder that regex/export-shape helpers and a minimal walker disagree on edge cases. Path helpers that sit next to resolvePath are covered in pathe vs node:path; URL shaping next to mlly's ufo dependency is in ufo vs url. Unused-export cleanup after analysis pairs with knip vs depcheck.

Which one should you pick?

SituationMy pick
List exports/imports from source text without an ASTmlly
Need Program AST, plugins, or custom visitorsacorn
Fastest parse of medium/large ESM on this boxacorn (about 2.7× on 240 B; near-tie at 34 KB)
Syntax kind scan only (ESM vs CJS)mlly detectSyntax
Resolve a specifier to a pathmlly resolvePath / resolvePathSync
Already writing estree transformsstay on acorn (or add a walker)

Bottom line: on this box mlly 1.8.2 findExports took about 0.046 ms on a 240 B file versus acorn 8.19.0 parse at about 0.017 ms, while mlly's analyzed bundle gzipped to 42.5 KB against acorn's 34.2 KB. Who should not switch to raw acorn: tooling authors who want findExports / detectSyntax / resolve without maintaining AST walks. Who should not pull all of mlly for a single parse: teams that already own an estree pipeline and only need parse + walk.

How this was made: I installed both packages on the ShopperCove box, timed findExports / imports / detectSyntax / resolve against acorn.parse / tokenizer / a small export walker over three fixtures × 9 rounds in two processes, bundled each entry with esbuild, and compared install trees. The JSON results sit next to the scripts. The write-up was drafted with AI help and checked against that output.

Sources

  • https://github.com/unjs/mlly
  • https://github.com/acornjs/acorn
  • https://www.npmjs.com/package/mlly
  • https://www.npmjs.com/package/acorn
  • https://esbuild.github.io/

Related

  • https://www.shoppercove.com/blog/esbuild-register-vs-jiti
  • https://www.shoppercove.com/blog/swc-vs-esbuild-babel
  • https://www.shoppercove.com/blog/terser-vs-esbuild-swc-minify
  • https://www.shoppercove.com/blog/pathe-vs-node-path
  • https://www.shoppercove.com/blog/ufo-vs-url
  • https://www.shoppercove.com/blog/size-limit-vs-bundlesize
  • https://www.shoppercove.com/blog/tsup-vs-unbuild
  • https://www.shoppercove.com/blog/knip-vs-depcheck
mllyacornesmjavascriptparserperformancebenchmarkingast

Lab evidence

What I found running this

Hands-on on ShopperCove box 6 Oct 2026 ~03:50 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 20.19.2). mlly 1.8.2 vs acorn 8.19.0; esbuild 0.28.2 node ESM minify + gzip -9. Fixtures 240/2,720/34,608 B. Median of 9 rounds × 5,000/2,000/500: findExports 0.0456/0.0459 vs parse 0.0176/0.0173; medium 0.344/0.346 vs 0.190/0.189; large 11.17/10.89 vs 10.33/10.20. detectSyntax 0.00042/0.00041; findStaticImports 0.0198/0.0203; tokenizer 0.0099/0.0101; resolvePathSync 0.0165/0.0169. Bundle gzip mlly 42,531 B vs acorn 34,173 B; install 1,572 vs 604 KB. Behavior: findExportNames listed App + default; simple AST walk listed default only. Not tested: TS/JSX plugins, magicast/recast, 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 97

    c12 vs cosmiconfig: 0.58 ms vs 1.08 ms per .mjs Load

    5 Oct 2026

  • Plate 08

    fast-glob vs globby: 0.7 ms vs 14 ms With gitignore

    5 Oct 2026

  • Plate 41

    ofetch vs ky: 4.1 KB vs 9.8 KB Gzip

    5 Oct 2026

On this page

  1. What I tested
  2. How fast is mlly findExports vs acorn.parse?
  3. What does each one cost to install and bundle?
  4. Do they behave the same?
  5. Which one should you pick?
  6. Sources
  7. Related
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove