npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vellora/wasm

v0.5.0

Published

WebAssembly build of the vellora HTML-to-PDF engine, for Cloudflare Workers and other runtimes that cannot load a native addon. Used automatically by the vellora package on workerd.

Readme

@vellora/wasm

The WebAssembly build of vellora's HTML-to-PDF engine, for runtimes that cannot load a native addon: Cloudflare Workers first.

You probably want vellora instead. On Cloudflare Workers, import { renderPdf } from "vellora" uses this package automatically (the workerd export condition); you only install it next to vellora:

npm install vellora @vellora/wasm

See examples/cloudflare-worker.

What's inside

The same Rust engine as @vellora/native, compiled to wasm32-unknown-unknown: Blitz layout, vellora's pagination and the krilla PDF writer, with the default fonts embedded. Same input, same PDF: the repository's tests render every fixture with both engines and require byte-identical output.

API

The same surface as @vellora/native:

import { render } from "@vellora/wasm";

const pdf = await render(new TextEncoder().encode(html), {
  title: "Invoice",
  creationDate: [2026, 6, 22],
  images: { "logo.png": logoBytes },
});
  • render(html, opts?) → Promise<Uint8Array>. Errors carry the same properties as the native engine: { feature, line, col, hint } for an out-of-subset construct, { profile, errors } for a PDF/A failure.
  • init(source?) compiles and instantiates the engine ahead of the first render. Pass a WebAssembly.Module or the .wasm bytes to load it from somewhere else.

The package picks the loader for its runtime through export conditions: workerd (Wrangler bundles the .wasm as a module), node (reads the file), and default (fetches the .wasm next to the script, for browsers).

Differences from the native engine

  • Single-threaded. A render runs on the calling thread and blocks it until the PDF is done.
  • Panics. A Rust panic aborts on wasm32. The render rejects with render panicked: … (the native wording) and the instance is re-created for the next call. On a browser's main thread that re-creation is refused for a module this size, so the engine stays unusable until the page reloads; render in a Web Worker to avoid it.

The full list is in the "WebAssembly build" section of COMPATIBILITY.md.

Size

Cloudflare Workers limits the uncompressed bundle to 64 MiB on both Free and Paid; there has been no compressed limit since 2026-09-04 (Workers limits, read 2026-11-03). This module fits on both plans with room to spare. What its size still costs is time: the module is read, compiled and instantiated on every cold start.

Measured on 2026-11-03 with npm run build:wasm (Rust 1.96.0, wasm-bindgen 0.2.125, profile wasm-release, opt-level = "s", fat LTO) and benchmarks/wasm-vs-native.mjs (invoice, Node.js 22.23.4, Linux, Intel Xeon ×4, 15 fresh processes for the cold start, 50 warm renders):

| | Raw | gzip -9 | Warm render (median) | Cold start (median) | |---|---|---|---|---| | Before issue #66 (LTO silently off) | 16.4 MiB | 6.4 MiB | 39.7–42.0 ms | 324–336 ms | | Shipped | 14.9 MiB | 6.1 MiB | 25.1–27.7 ms | 325–342 ms |

PDFs are byte-identical to the native addon in both. Until issue #66 the profile's lto = true did nothing: cargo turns LTO off for a library whose crate types include rlib, and crates/vellora-wasm listed one. ryu's small feature on wasm32 saves another 9 KB.

Measured and not shipped (same harness, PDFs compared on every fixture plus SVG, font, script and image probes):

| Lever | Raw saving | Why not | |---|---|---| | Fonts stored brotli-compressed, decoded in the engine (measured before the LTO fix) | −3.6 MB (−1.0 MB gzip) | First render 45–180 ms slower (decoding) | | Unify the font stack (krilla/subsetter on skrifa 0.44) | −318 KB | Needs vendored crates; read-fonts 0.41 bug fixes change a few malformed fonts' PDFs | | Blitz's usvg without <text> (never runs: Blitz only sizes SVGs) | −596 KB | Needs a vendored blitz-dom; no measurable speed gain | | Unify usvg on 0.48 | −1.1 MB | Changes nested <svg transform> output (a usvg 0.48 fix) | | opt-level = "z" for crates a typical document never runs | −284 KB | Not faster; slower for SVG-heavy documents | | wasm-opt (binaryen 132, -O1 … -Oz; measured before the LTO fix) | up to −1.5 MB | Slower first render and warm p95 |

The cold start is V8 compiling code, not the module's size: on a fresh isolate V8 compiles each function on first call, and the invoice's first render compiles about 2.3 MiB of code (Stylo, vellora's layout, Blitz, krilla, html5ever, Taffy, harfrust) out of 8.1 MiB. One-time Rust initialisation is about 7 ms. Cutting code that never runs makes the file smaller but does not make that first render faster. Measure it with:

cargo build -p vellora-wasm --target wasm32-unknown-unknown --profile wasm-profiling
node packages/wasm/scripts/code-size.mjs                         # code per crate
node packages/wasm/scripts/code-size.mjs --first-render invoice  # what the first render compiles

About 3.1 MB of the gzip size (5.8 MB raw) is the fourteen embedded font files.

Build from source

rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --version 0.2.125 --locked   # must match Cargo.lock
npm run build:wasm   # from the repository root

License

MIT. The embedded fonts are under their own licenses; see THIRD-PARTY-NOTICES.md.