Plate 38
smol-toml vs @iarna/toml: 3x Faster on Deno Cargo
Aditya Challa5 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.
What I tested
- Machine: 8 vCPU Intel Xeon / 15 GB RAM shared Linux cloud box, tested 6 Oct 2026 about 01:35 IST.
- Versions: smol-toml 1.9.0 (published 22 Sep 2026), @iarna/toml 2.2.5 (last published Jul 2023), Node 22.20.0, esbuild 0.28.2 minify + gzip -9.
- Inputs: Deno v2.1.4 Cargo.toml (11,382 B), Grafana ldap.toml (3,137 B), and a node-gyp pyproject.toml (2,936 B). Scripts and results are in /workspace/bench-smol-toml-iarna/.
- Method: many ops per timed round (200–1,000), median of 9 rounds, two runs with the library order reversed.
- Findings: Deno Cargo parse 0.159 / 0.153 ms (smol) vs 0.479 / 0.458 ms (iarna). Grafana ldap 0.018 vs 0.045 ms. pyproject 0.022 vs 0.072 ms. Stringify on the same objects was about 3× faster for smol too.
- One surprise: @iarna/toml pulls Node's stream module, so a plain browser esbuild failed until I marked stream external. smol-toml bundled cleanly.
- Not tested: browsers at runtime, Bun or Deno as hosts, TOML 1.1 extras, streaming parse, or untrusted / adversarial input.
Is smol-toml faster than @iarna/toml?
| 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.
How much smaller is the bundle?
| 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.
Do they parse the same way?
| 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.
Which one should you pick?
| 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.
Sources
- https://github.com/squirrelchat/smol-toml
- https://github.com/iarna/iarna-toml
- https://www.npmjs.com/package/smol-toml
- https://www.npmjs.com/package/@iarna/toml
- https://raw.githubusercontent.com/denoland/deno/v2.1.4/Cargo.toml
Related
- https://www.shoppercove.com/blog/yaml-vs-js-yaml
- https://www.shoppercove.com/blog/size-limit-vs-bundlesize
- https://www.shoppercove.com/blog/pathe-vs-node-path
- https://www.shoppercove.com/blog/defu-vs-lodash-merge
- https://www.shoppercove.com/blog/zod-vs-valibot
- https://www.shoppercove.com/blog/ajv-vs-zod
- https://www.shoppercove.com/blog/knip-vs-depcheck
- https://www.shoppercove.com/blog/lefthook-vs-husky
Lab evidence
What I found running this
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.