Skip to content

Footprint

These results measure an esbuild-minified browser bundle for each imported codec API. Validation libraries are excluded from every row.

CodecMinifiedGzip
shorn core + Standard adapter17.57 KB5.39 KB
@msgpack/msgpack21.20 KB5.93 KB
msgpackr27.59 KB10.39 KB
cbor-x29.10 KB10.82 KB
protobufjs/light88.35 KB25.93 KB

shorn is the smallest codec measured, but the gzip margin over @msgpack/msgpack is now 9%, not the 27% earlier revisions reported. Size is a lead worth defending, not a settled one. avsc needs a browser stream polyfill, and SchemaPack needs a buffer polyfill, so the harness does not report a zero-polyfill result for either. The shorn npm tarball is 41,432 bytes, of which the source map is roughly two thirds; it is published for debugging and never reaches a bundle.

import setminifiedgzipthis row adds
codec17,3895,383
+ m17,9695,523140 gzip
+ safeEncode / safeDecode18,2575,59471 gzip
+ encodeAsync / decodeAsync18,5705,66672 gzip
+ fingerprinted19,7686,061395 gzip
everything20,2686,232171 gzip

Only users who import a feature pay for it. Fingerprinting is the most expensive single import at 395 gzip bytes, and a bundle that never calls fingerprinted() never carries it.

Two structural decisions keep that true:

  • Async validation is a free function, not a Schema method, because class methods do not tree-shake — every method on Schema is retained by every bundle that touches a codec. See ADR 0001.
  • The Standard Schema compiler constructs wire schemas directly rather than through m. Referencing m pinned the whole builder object into every bundle, including bytes and float32, which no JSON Schema type can ever select. Removing that reference took 607 minified and 145 gzip bytes off a codec-only import.

The one place tree-shaking stops is m itself: it is a single object, so import { m } retains all twelve builders whether you call two or twelve. That is a deliberate trade — see ADR 0004.

Add your validator on top. shorn does not ship one, which is the point — you were already paying for Zod.

Schema and codec construction plus the first Person encode.

CodecCold setup
JSON0.08 µs
msgpackr records1.00 µs
SchemaPack3.00 µs
shorn + Zod52–66 µs
Avro / avsc68.99 µs
Protobuf.js reflection187.75 µs

shorn starts faster than Avro but slower than SchemaPack. Most of shorn’s time is Zod schema construction, which applications using Zod already pay. The cost is usually negligible in a long-lived server but can matter in a serverless function that handles one request. Define schemas at module scope so warm invocations reuse them.

Steady-state retained memory after repeated forced GC in isolated processes, for 100,000 decoded events. Does not measure transient peak allocation.

CodecPayloadEncode retainedDecoded valueRSS increase
shorn4.04 MiB4.11 MiB36.66 MiB72.80 MiB
Avro4.14 MiB4.20 MiB33.64 MiB61.22 MiB
SchemaPack4.13 MiB4.17 MiB47.82 MiB64.17 MiB
msgpackr records4.76 MiB17.05 MiB32.44 MiB62.20 MiB
JSON15.58 MiB15.58 MiB24.03 MiB88.69 MiB

Encoding a 4.04 MiB payload retains 4.11 MiB. This is down from 12.10 MiB before encoded output became an exact-size copy. Retaining the result no longer retains a larger backing buffer, and internal buffers larger than 64 KiB are released. These rules also make it safe to reuse one pooled Writer.

Decoded memory is middle of pack, RSS below JSON but above Avro and msgpackr records.

shorn targets es2022 with esbuild’s neutral platform setting. It is ESM-only and uses no Node built-ins, so it runs in Node 22+, Bun, Deno, browsers, and workers. Nothing in the library needs Node 22 specifically — the floor is set by what CI can prove, and the pinned pnpm no longer runs on Node 20.

The full comparison and a smoke test also ran under Bun 1.3.14. Rankings changed between runtimes, so publish runtime-specific numbers with their runtime and version. There is no browser execution matrix yet — only bundling is measured; see Hostile Input for every unproven claim.

Terminal window
pnpm bench:bundle # bundle sizes per import set
pnpm bench:startup # cold setup
pnpm bench:memory # retained memory in isolated processes