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

@synapticforge1/universal-wasm-loader

v1.1.1

Published

A zero-dependency WebAssembly loader: load a .wasm like an ES import, with its .wit applied through the Canonical ABI. Node, Bun, Deno and browsers. Also on JSR as @jrmarcum/universal-wasm-loader.

Readme

universal-wasm-loader

A lightweight, zero-dependency WebAssembly loader for JavaScript, TypeScript, and the Web. Works in Node.js 22.18+, Bun, Deno, and all modern browsers without any configuration or bundler setup. Every release is tested on every current Node LTS line, on Bun and on Deno.

Published to two registries with the same code under the same version number: npm as @synapticforge1/universal-wasm-loader and JSR as @jrmarcum/universal-wasm-loader, both with build provenance (from 1.1.1 on npm).

Installation

From npm

npm install @synapticforge1/universal-wasm-loader   # or: bun add / pnpm add / yarn add
import { wasmImport } from "@synapticforge1/universal-wasm-loader";

No .npmrc or extra registry is needed.

From JSR

Install via JSR:

Deno:

deno add jsr:@jrmarcum/universal-wasm-loader

npm:

npx jsr add @jrmarcum/universal-wasm-loader

Bun:

bunx jsr add @jrmarcum/universal-wasm-loader

pnpm:

pnpm dlx jsr add @jrmarcum/universal-wasm-loader

Yarn:

yarn dlx jsr add @jrmarcum/universal-wasm-loader

vlt:

vlt install jsr:@jrmarcum/universal-wasm-loader

After installing, the import line is the same on every runtime:

import { wasmImport } from "@jrmarcum/universal-wasm-loader";

npx jsr add and bunx jsr add write the dependency to package.json and the @jsr registry line to .npmrc; commit both. Importing jsr:@jrmarcum/universal-wasm-loader with no install step works on Deno only: Node and Bun do not resolve the jsr: prefix.

The examples below import the JSR name; with the npm package, import @synapticforge1/universal-wasm-loader instead. Everything else is identical.

Usage

Basic — destructure exports

import { wasmImport } from "@jrmarcum/universal-wasm-loader";

const { greet, isEven } = await wasmImport("./mod.wasm");

console.log(greet("World")); // "Hello, World!"
console.log(isEven(4));      // true

Namespace style

const m = await wasmImport("./mod.wasm");

m.greet("World");
m.isEven(4);

Version pinning

Append @N to pin to a specific major version. The loader checks the module's exported version global and throws a descriptive error if it doesn't match — the same convention C shared libraries use with SONAME major versioning.

const { greet } = await wasmImport("./mod.wasm@2");
// Throws if the module's exported `version` global !== 2

With host import callbacks

When your WASM module calls back into JS, pass the host functions as a flat camelCase object. The loader maps WIT kebab-case names (env-mul) to camelCase JS keys (envMul) automatically.

const { scale, combine } = await wasmImport("./math.wasm", {
  envMul: (a, b) => a * b,
  envAdd: (a, b) => a + b,
});

console.log(scale(3.0, 4.0)); // 12.0

How It Works

  1. Resolves a relative .wasm path against the loader's own location (its import.meta.url); a URL or absolute path is used as given (see Where your modules live)
  2. Auto-detects the companion .wit file by replacing .wasm → .wit
  3. Applies the Canonical ABI (wasmtime) — bool and string types handled automatically
  4. Reads a file: URL from disk on Node, Bun and Deno (Node's fetch does not accept file: URLs); otherwise uses WebAssembly.instantiateStreaming for best performance, falling back to fetch + arrayBuffer
  5. Returns an ABI-translated proxy keyed by camelCase WIT export names

If no .wit file is found, raw WebAssembly.Exports are returned.

API

wasmImport(wasmPath, hostCallbacks?)

| Parameter | Type | Description | | --- | --- | --- | | wasmPath | string \| URL | Path or URL of the .wasm file. A relative string resolves against the loader's own location. Append @N to pin to a major version. | | hostCallbacks | Record<string, Function> | Host functions the WASM module calls into JS. Flat object, camelCase keys. |

Where your modules live

Keep your .wasm modules (and their .wit files) in whatever folder suits your project, such as wasm_modules/, and pass a URL built from your file's import.meta.url:

const { greet } = await wasmImport(new URL("./wasm_modules/mod.wasm", import.meta.url));
const pinned = await wasmImport(new URL("./wasm_modules/mod.wasm@2", import.meta.url));

A plain relative string such as "./mod.wasm" resolves against the loader's own file. That is right only when the module sits next to the loader; from an installed package, that location is inside node_modules.

Returns: Promise<Record<string, Function>> — ABI-translated proxy keyed by camelCase WIT export names, or raw WebAssembly.Exports if no .wit file is found.

Type mapping

| WIT type | JS → WASM | WASM → JS | | --- | --- | --- | | s32, s64, f32, f64 | pass as-is | return as-is | | bool | value ? 1 : 0 | result !== 0 | | string | UTF-8 encode → cabi_realloc → write → pass (ptr, len) | export returns an i32 ptr to a callee-allocated [ptr, len] pair; read via DataView, then call cabi_post_<name> |

See SPEC.md for the full cross-language conformance specification.

Library modules & I/O (SPEC §10)

The loader consumes reactor/library modules (e.g. wasmtk modc output). It calls the module's _initialize export once after instantiation if present, and always supplies a minimal built-in WASI Preview 1 shim, so a library that does I/O (e.g. console.log → fd_write) instantiates even in the browser. fd_write routes stdout→console.log and stderr→console.error; a pure-compute library imports nothing and is unaffected.

createSingleton(wasmPath, hostCallbacks?)

Returns an accessor function that loads the WASM instance on the first call and caches it for all subsequent calls.

const getMod = createSingleton("./mod.wasm");
const { greet } = await getMod();   // loads on first call
const same = await getMod();        // returns cached instance

Appropriate for CLI tools and bounded-call scenarios.

InstancePool(wasmPath, hostCallbacks?, size?)

Pre-instantiates size (default: 4) independent WASM instances and manages acquire/release semantics for concurrent workloads.

const pool = new InstancePool("./mod.wasm", {}, 4);
const result = await pool.run(mod => mod.compute(42));

| Method | Description | | --- | --- | | acquire() | Check out an instance. Waits if all are in use. | | release(instance) | Return an instance to the pool. | | run(fn) | Acquire, call fn(instance), release — even on throw. |

Appropriate for servers and loop-intensive workloads.

TypeScript

Full TypeScript support is included. No @types package needed.

import { wasmImport } from "@jrmarcum/universal-wasm-loader";
import type { HostCallbacks } from "@jrmarcum/universal-wasm-loader";

const callbacks: HostCallbacks = { envMul: (a, b) => (a as number) * (b as number) };
const { scale } = await wasmImport("./mod.wasm", callbacks);

Versioning

This package follows Semantic Versioning 2.0.0. Every bump is gated: the release tooling compares the public API (api-surface.json, generated from the .d.ts files and the SPEC.md version) with the previous release, and refuses a bump lower than the change requires. It also runs the previous release's tests against the new code.

⚠️ Breaking Changes

| Version | What changed | How to migrate | | --- | --- | --- | | — | No breaking change since the compatibility gate began (1.1.0, 2026-10-12). | — |

Publishing

One tag push (v*) publishes to JSR and then to npm from GitHub Actions, with OIDC provenance on both (npm through trusted publishing) — no stored token.

To release a new version: bun run bump [patch|minor|major] (raises version in jsr.json, the package manifest), then bun run release.

Development

Bun is the development runtime; the code and tests also run on Node and Deno.

bun install          # dev dependencies (TypeScript, @types/node, @std/assert)
bun run gate         # type check (incl. the shipped JS), Biome lint, manifest, API-surface
                     # snapshot, fixture stamp, browser check, tests on Bun
bun run compat       # API floor since the last release + that release's tests on this code
bun run test:node    # the same tests on Node   (node --test; needs Node on PATH, see below)
bun run test:deno    # the same tests on Deno   (deno test)
bun run lts          # the tests on every current Node LTS line, through fnm

When no node is on PATH, bun run substitutes Bun for node, so bun run test:node would run Bun (it then fails with "Cannot use describe outside of the test runner"). Put a Node on PATH first, e.g. fnm exec --using=24 bun run test:node.