Skip to content

WebAssembly vs JavaScript XML parsing

These are exploratory measurements from 23 September 2026 on an AMD Ryzen 5 4500 (6 cores), using Node 26.8.2, Bun 1.4.2, and headless Chromium 145. The fixture repeats <item id="42"><name>Ada &amp; Bob</name><active>true</active></item> inside <items>. Its three sizes are 3,075 bytes, 1,048,643 bytes, and 52,428,831 bytes. The scripts and exact run commands are in benchmarks/typescript-wasm.

The table reports median end-to-end milliseconds for parsing into JavaScript objects. Lower is better. PolyXML includes Wasm transfer, XML to JSON transcoding, JSON transfer, and JSON.parse. fast-xml-parser 5.11.1 uses numeric scalar parsing and attributes. The cases are similar but do not have identical output conventions.

Runtime Fixture PolyXML object fast-xml-parser object Ratio
Node 3 KB 0.244 ms 1.578 ms 6.5×
Node 1 MB 36.37 ms 181.54 ms 5.0×
Node 50 MB 1,947.6 ms 8,629.1 ms 4.4×
Bun 3 KB 0.177 ms 0.456 ms 2.6×
Bun 1 MB 31.93 ms 145.66 ms 4.6×
Bun 50 MB 1,716.5 ms 7,620.6 ms 4.4×
Chromium 3 KB 0.2 ms 0.5 ms 2.5×
Chromium 1 MB 40.6 ms 134.7 ms 3.3×
Chromium 50 MB 2,136.5 ms 6,867.3 ms 3.2×

The small and medium cases used 100 and 15 iterations. The 50 MB cases used separate processes with 3 to 5 iterations. Those large runs and the browser sub-millisecond timer resolution limit the precision of the ratios. The benchmark also includes xml2js 0.6.2 and browser DOMParser, but their result shapes differ substantially; compare those using the raw suite output.

The generated Wasm binary is 460,190 bytes (167,949 bytes gzip). The generated Wasm JS glue, browser wrapper, and stream helper total another 20,253 bytes uncompressed and 5,397 bytes gzipped separately. These are file sizes, not a minified application bundle. Browser bundlers may change the delivered size. wasm-bindgen copies bytes across the JS/Wasm boundary, and object conversion allocates in JavaScript. The stream API also buffers one complete record at a time; it is most useful when a document has many separate records.

The Node runner samples heap deltas and observes GC entries, but this local run did not yield usable per-case GC pause data. Bun heap deltas were frequently zero and are not comparable to Node heap deltas. We therefore make no GC reduction claim. This repeated-record workload does not establish performance for deeply nested XML, attributes-heavy documents, schemas, or tiny webhook payloads in production. Run the suite on representative inputs before choosing a parser solely for speed.