fast-packages

Drop-in packages that give the same answers, sooner.

Seven npm packages, including pako, acorn and esbuild, rebuilt to return byte-identical results in a fraction of the time, in Node and in the browser. They were made for Nodepod, a Node.js runtime that runs in the browser, and work anywhere the originals do. Every comparison here runs live in your browser.

Race

    Not on this list: esbuild. A fair race needs the original's 14 MB download, so its page asks first. Each timing here is the best of several short interleaved rounds on your machine, so expect your own numbers to differ from the tables below.

    Three rules every package follows

    The same results

    Byte-identical output, identical syntax trees, identical errors and callbacks. Each package is checked against the original on real code: 126,258 esbuild outputs, 93,841 acorn parses, 290,446 Brotli streams, and more, listed under how we check.

    Faster on the benchmarks

    A change only stays if the calls it touches still beat the original and none gets more than 10% slower than the previous version of the package. Size savings that broke this rule were rejected, even when they saved bytes.

    No original inside

    None of the packages ships the original as a fallback. What you install is the replacement alone, which is also why most of the npm downloads are smaller: pako's, for one, is 43.5 KB instead of 412.5 KB.

    How much faster

    Recorded on a Ryzen 7 7800X3D, in Node and Chromium, best of three rounds in fresh processes. Each row of every table is on its package's page.

    fast-pako
    2.1 to 20x
    fast-acorn
    2.2 to 3.2x
    fast-esbuild-wasm
    2.4 to 10x
    fast-brotli-wasm
    1.5 to 13x
    fast-noble-hashes
    1.2 to 7.8x
    1x2x5x10x20x

    Each bar spans a package's smallest to largest speedup across the rows of its results table. The dark tick is the median row.

    The packages

    Each page has a live comparison, an explanation of what made it faster, and where the limits are.

    How they got faster

    Five ideas account for nearly all of it. Each package uses a few of them.

    Move the hot loop into WebAssembly

    Used in pako, es-module-lexer, brotli and noble-hashes

    A JavaScript engine has to guess types and guard those guesses. WebAssembly is typed and compiled ahead of time, has real 64-bit integers, and offers SIMD, which processes 16 bytes per instruction.

    That is why SHA-512 gains about 5x, since JavaScript has to build 64-bit arithmetic from pairs of 32-bit numbers, while SHA-256, which a JIT already handles well, gains about 1.7x.

    Rewrite for what the JIT is good at

    Used in acorn and esbuild

    Engines run code fastest when objects have one consistent shape and values keep one type. fast-acorn gives every syntax-tree node type its own constructor and uses integers for token types, but keeps acorn's exact output.

    esbuild-wasm is Go compiled to WebAssembly, which carries Go's runtime and a 14 MB download. Porting esbuild itself to JavaScript removes both.

    Pay only for what you use

    Used in every package

    Tables are built the first time something needs them: pako's checksum and tree tables, Brotli's 518 KB of encoder lookup tables, each hash family's WebAssembly kernel. Modules that most callers never touch, such as acorn's generic parser for unusual plugins, load on demand.

    The result is a smaller download and a start-up that costs close to what the original's did.

    Copy the behaviour, then prove it

    Used in every package

    The ports follow the originals function by function, including their quirks, so decisions such as which match zlib emits next come out the same. Then differential tests feed the same inputs to both implementations and compare everything observable: outputs, errors, even the state of instances.

    fast-noble-hashes is also mutation tested: deliberate bugs are planted in it, and the suite must catch all 41. See how we check.

    Measure honestly, keep the failures

    Used in every package

    Benchmarks alternate the two implementations in fresh processes and keep the best of several rounds, so background load hits both alike. Ideas that did not work are written down instead of deleted: the dead ends explain, for example, why deflate cannot go much past 2x without changing its output.

    Using them

    Install each replacement under the name of the package it replaces, so every import, including those inside your other dependencies, gets the fast version. The major and minor versions match the originals, and the APIs and results are the originals', so this works the same in a bundler config, a Node project, a command-line tool or a browser app.

    "dependencies": {
      "pako": "npm:@r1ck404/fast-pako@2.1.2",
      "acorn": "npm:@r1ck404/fast-acorn@8.18.2",
      "acorn-jsx": "npm:@r1ck404/fast-acorn-jsx@5.3.4",
      "esbuild-wasm": "npm:@r1ck404/fast-esbuild-wasm@0.28.5",
      "es-module-lexer": "npm:@r1ck404/fast-es-module-lexer@1.7.2",
      "brotli-wasm": "npm:@r1ck404/fast-brotli-wasm@3.0.3",
      "@noble/hashes": "npm:@r1ck404/fast-noble-hashes@1.8.2"
    }

    pnpm can apply the same specs under pnpm.overrides to redirect transitive dependencies, and a bundler alias such as Vite's resolve.alias works too.

    The packages were made for Nodepod, which does not use them yet. Versions follow the original's major and minor, so peer dependency ranges such as acorn-jsx's acorn ^8 keep working; the patch number is this project's own.