Plate 18
yaml vs js-yaml: 11x Faster Parse vs Kept Comments
Aditya Challa6 min read
I parsed hono's real 332 KB pnpm-lock.yaml on this box: js-yaml 5.4.2 took about 11–12 ms, while yaml 2.9.1 took about 126–128 ms. That is roughly 11× faster, and js-yaml is also smaller at 17 KB gzip versus 31 KB.
Short answer: use js-yaml when you only read or write YAML data (lockfiles, CI configs, front matter) and speed or bundle size matters. Use yaml when you edit YAML files that humans maintain, because it was the only one of the two that kept my comments after a change. 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:30 IST.
- Versions: yaml 2.9.1, js-yaml 5.4.2, Node 22.20.0, esbuild 0.28.2 browser ESM minify + gzip -9.
- Fixtures: the pnpm-lock.yaml (332,558 bytes, two YAML documents) and .github/workflows/ci.yml (10,831 bytes) from a hono checkout at commit 5f36607 (5 Oct 2026). Files and scripts are in /workspace/bench-yaml-js-yaml/.
- Method: 9 timed rounds after warm-up, median per operation, two runs with the library order reversed.
- Findings: lockfile parse 11.3 / 11.8 ms (js-yaml) vs 126.2 / 127.8 ms (yaml). ci.yml parse 0.26 ms vs 2.2 ms. Stringify of the lockfile object 15.5 / 14.5 ms vs 40.9 / 42.3 ms.
- One surprise: plain load() and parse() both refused the lockfile. This pnpm 12.6.0 lockfile holds two YAML documents, so you need loadAll() or parseAllDocuments().
- Not tested: browsers, Bun or Deno, streaming huge files, custom tags, untrusted input hardening, and js-yaml 4.x (the older line many projects still pin).
Is js-yaml faster than yaml?
| Operation (median ms, 2 runs) | js-yaml 5.4.2 | yaml 2.9.1 | Gap |
|---|---|---|---|
| Parse pnpm-lock.yaml, 332 KB, 2 docs | 11.3 / 11.8 | 126.2 / 127.8 | about 11× |
| Parse ci.yml, 10.8 KB | 0.26 / 0.26 | 2.20 / 2.24 | about 8.6× |
| Stringify lockfile object | 15.5 / 14.5 | 40.9 / 42.3 | about 2.6–2.9× |
Yes, by a wide margin on parsing. yaml builds a full document tree (with positions and comments) before it hands you plain objects, and you pay for that even when you only want data. If a script reads lockfiles across a monorepo, that gap adds up fast. Lockfile tooling context is in my pnpm 12.9.1 notes and Yarn Berry vs pnpm.
How much bigger is yaml in a browser bundle?
| Build (esbuild browser minify) | Minified | gzip -9 |
|---|---|---|
| yaml parse + stringify | 98,088 B | 30,635 B |
| js-yaml load + dump | 57,073 B | 17,199 B |
| yaml parse only | 97,760 B | 30,518 B |
| js-yaml load only | 46,378 B | 13,658 B |
js-yaml tree-shakes: importing only load() dropped it to 13.7 KB gzip. yaml stayed at about 30.5 KB even with parse alone. If YAML parsing ships to the browser (a config editor, a docs playground), put a budget on it with size-limit vs bundlesize.
Do they read the same values?
| Input | yaml 2.9.1 default | js-yaml 5.4.2 default |
|---|---|---|
| country: no | "no" (string) | "no" (string) |
| on: push (GitHub Actions key) | key "on" | key "on" |
| mode: 0755 | 755 | 755 |
| released: 2026-10-06 | "2026-10-06" (string) | "2026-10-06" (string) |
| node: 22.10 | 22.1 (number) | 22.1 (number) |
| Merge key <<: *b | literal "<<" key | literal "<<" key |
| Duplicate key a: 1 / a: 2 | error | error |
Both defaulted to YAML 1.2 core rules in my test, so the old "Norway problem" did not appear. With YAML 1.1 turned on (yaml version: '1.1', js-yaml YAML11_SCHEMA), both read no as false, 0755 as 493, the date as a Date, and both applied the merge key. One trap stays the same: unquoted 22.10 becomes 22.1 in both, so quote version strings. Validating the parsed object is a separate job; I compared validators in Zod vs Valibot.
Which one keeps comments when you edit a file?
| Edit runs-on in a 5-line commented CI file | Comments left | First line of output |
|---|---|---|
| yaml parseDocument + setIn + toString | 3 of 3 | # CI config |
| js-yaml load + edit + dump | 0 of 3 | 'on': push |
This is the real decision. yaml's document API changed one value and left comments, order and the inline note alone. js-yaml rebuilt the file from a plain object, dropped every comment, and quoted the on key. For a bot that bumps versions in committed YAML (think hook configs like the ones in Lefthook vs Husky), use yaml.
Error messages were close: yaml said "Nested mappings are not allowed in compact mappings at line 1, column 4" and js-yaml said "bad indentation of a mapping entry (2:3)" for the same broken input.
Which one should you pick?
| Situation | My pick |
|---|---|
| Read lockfiles or CI files in scripts | js-yaml (loadAll for pnpm lockfiles) |
| YAML in a browser bundle | js-yaml, load only |
| Edit human YAML and keep comments | yaml (parseDocument) |
| Need line and column for every node | yaml |
| Write fresh YAML from data | either; js-yaml was about 2.7× faster |
Bottom line: js-yaml parsed real files 8–11× faster and costs about half the bundle. Who should not switch to it: anyone writing back to files people maintain by hand, because the comments go. If you drop one parser, run knip vs depcheck afterwards to confirm nothing still imports it.
How this was made: I installed both packages on the ShopperCove box, copied two real YAML files from a hono checkout, timed parse and stringify in two order-reversed runs, measured esbuild bundle sizes, and checked value, comment and error behavior. The JSON results are saved next to the scripts. The write-up was drafted with AI help and checked against that output.
Sources
- https://github.com/eemeli/yaml
- https://eemeli.org/yaml/
- https://github.com/nodeca/js-yaml
- https://www.npmjs.com/package/yaml
- https://www.npmjs.com/package/js-yaml
Related
- https://www.shoppercove.com/blog/pnpm-12-9-1-rust-rewrite-wasm-split-october-2026
- https://www.shoppercove.com/blog/yarn-berry-vs-pnpm
- https://www.shoppercove.com/blog/size-limit-vs-bundlesize
- https://www.shoppercove.com/blog/zod-vs-valibot
- https://www.shoppercove.com/blog/lefthook-vs-husky
- https://www.shoppercove.com/blog/knip-vs-depcheck
- https://www.shoppercove.com/blog/ajv-vs-zod
- https://www.shoppercove.com/blog/marked-vs-markdown-it
Lab evidence
What I found running this
Hands-on on ShopperCove box 6 Oct 2026 ~01:30 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). yaml 2.9.1 vs js-yaml 5.4.2, esbuild 0.28.2 browser ESM minify + gzip -9. Fixtures copied from hono checkout commit 5f36607 (5 Oct 2026): pnpm-lock.yaml 332,558 B with 2 YAML documents, .github/workflows/ci.yml 10,831 B. Speed (9-round median, order-reversed runs, ms/op): lockfile parse (loadAll vs parseAllDocuments+toJS) js-yaml 11.326 / 11.831 vs yaml 126.174 / 127.797; ci.yml parse 0.259 / 0.261 vs 2.204 / 2.244; stringify lock main doc 15.515 / 14.462 vs 40.861 / 42.271. Size: yaml 98,088/30,635 B (parse-only 97,760/30,518); js-yaml 57,073/17,199 B (load-only 46,378/13,658). Behavior (res-behavior.json, res-behavior-yaml11.json): both default YAML 1.2 core (no -> "no", 2026-10-06 string, << literal key, 22.10 -> 22.1, duplicate keys error); YAML 1.1 opt-in on both gives false/Date/493/merge. Comment edit: yaml 3/3 kept, js-yaml 0/3 and quotes on key. load()/parse() both reject the 2-doc lockfile. Not tested: browsers, Bun/Deno, js-yaml 4.x, custom tags, untrusted input. No affiliate.