5 min read
I parsed Deno v2.1.4's real Cargo.toml (11,382 bytes, 34 workspace members) on this box: smol-toml 1.9.0 took about 0.16 ms per parse, while @iarna/toml 2.2.5 took about 0.47 ms. That is roughly 3× faster, for a 6.9 KB gzip browser bundle instead of 8.7 KB on Node.
Short answer: use smol-toml for new config loaders, Vite-style tooling and anything that might ship to the browser. Keep @iarna/toml only if you already depend on its familiar JSON-shaped API everywhere and do not need a clean browser build. No affiliate links in this post.
| Input (median ms/op, 2 order-reversed runs) | smol-toml 1.9.0 | @iarna/toml 2.2.5 |
|---|---|---|
| Deno Cargo.toml, 11,382 B, parse | 0.159 / 0.153 | 0.479 / 0.458 |
| Grafana ldap.toml, 3,137 B, parse | 0.018 / 0.018 | 0.045 / 0.045 |
| node-gyp pyproject.toml, 2,936 B, parse | 0.022 / 0.023 | 0.071 / 0.072 |
| Deno Cargo.toml, stringify | 0.154 / 0.145 | 0.483 / 0.476 |
Yes, about 3× on the Cargo.toml and the small configs. If you already compared YAML parsers for lockfiles, the same speed-vs-features trade shows up in yaml vs js-yaml.
| Build (esbuild minify + gzip -9) | Minified | gzip |
|---|---|---|
| smol-toml parse+stringify (browser) | 17,303 B | 6,861 B |
| smol-toml parse only (browser) | 13,704 B | 5,578 B |
| @iarna/toml parse+stringify (Node platform) | 39,821 B | 8,746 B |
smol wins on size and on browser packaging. @iarna/toml needs Node stream (or a polyfill) for a browser bundle. Track the delta in CI with size-limit vs bundlesize. Path helpers next to a TOML config loader are a separate choice; see pathe vs node:path.
| Case | smol-toml | @iarna/toml |
|---|---|---|
| Deno Cargo workspace.members length | 34 | 34 |
| Basic types, dotted keys, inline tables, array-of-tables | match | match |
| Offset datetime | TomlDate (Date subclass) | Date |
| Duplicate keys | throws | throws |
| Comments after parse→stringify | lost (0/2) | lost (0/2) |
Parsed values matched on every fixture I tried. The practical difference is the datetime class: both are instanceof Date and JSON.stringify to the same ISO string, but code that checks constructor.name will see TomlDate vs Date. Neither library keeps comments when you round-trip through a plain object. For schema checks on the parsed object, pair this with zod vs valibot or ajv vs zod.
| Situation | My pick |
|---|---|
| New Node or universal config loader | smol-toml |
| Browser or edge bundle | smol-toml |
| Existing @iarna/toml call sites, no browser need | stay |
| Need comment-preserving edits | neither (look for an AST/document API elsewhere) |
| Merging defaults into parsed config | see defu vs lodash.merge |
Bottom line: smol-toml was about 3× faster and easier to ship than @iarna/toml on this box, with matching parse results on real Cargo, Grafana and pyproject files. Who should not switch: teams pinned to @iarna/toml types across a large codebase with no size or browser pressure. Unused TOML deps after a migration belong in the same cleanup pass as knip vs depcheck.
How this was made: I installed both packages on the ShopperCove box, timed parse and stringify on three real fixtures in two order-reversed runs, measured esbuild bundle sizes (including the stream failure for @iarna/toml in the browser), and compared types, duplicates and comment round-trips. The JSON results sit next to the scripts. The write-up was drafted with AI help and checked against that output.
Lab evidence
Hands-on on ShopperCove box 6 Oct 2026 ~01:35 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). smol-toml 1.9.0 vs @iarna/toml 2.2.5, esbuild 0.28.2 minify + gzip -9. Fixtures: Deno v2.1.4 Cargo.toml 11,382 B (raw.githubusercontent.com/denoland/deno/v2.1.4/Cargo.toml), Grafana ldap.toml 3,137 B, node-gyp pyproject.toml 2,936 B. Speed (multi-op rounds, 9-round median, order-reversed, ms/op): Cargo parse smol 0.15879 / 0.15303 vs iarna 0.47875 / 0.45763; ldap 0.01795 / 0.01781 vs 0.04531 / 0.04537; pyproject 0.02242 / 0.02255 vs 0.07105 / 0.0717; Cargo stringify 0.15377 / 0.14525 vs 0.48282 / 0.47566. Size: smol browser 17,303/6,861 B (parse-only 13,704/5,578); iarna Node 39,821/8,746 B; browser build fails without stream external. Behavior: both parse 34 workspace.members; TomlDate vs Date; duplicate keys throw; comment round-trip 0/2 both. Not tested: browsers runtime, Bun/Deno hosts, streaming, adversarial input. No affiliate.