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

@elchika-inc/moonqr

v0.2.0

Published

QR code encoder and decoder written in MoonBit. No dependencies, works in the browser.

Readme

@elchika-inc/moonqr

QR code encoder and decoder written in MoonBit, compiled to plain JavaScript. No runtime dependencies. Works in Node.js and the browser.

  • encode / toSvgString — text → QR matrix → SVG string.
  • decode — RGBA pixels → QR text (locates, extracts, corrects, and decodes; handles rotation, inverted color schemes, and photographed QR codes).
  • toCanvas — draw a QR matrix onto an HTMLCanvasElement.

For a live camera scanner built on top of this package, see @elchika-inc/moonqr-scanner.

Install

npm install @elchika-inc/moonqr

Usage

Encode

import { encode, toSvgString } from "@elchika-inc/moonqr/encode";

const matrix = encode("HELLO", { ecLevel: "M" });
if (matrix) {
  console.log(matrix.size); // 21 (v1)
  console.log(matrix.get(0, 0)); // true (top-left finder pattern)
  const svg = toSvgString(matrix, { margin: 4, cell: 8 });
}

encode() returns null on capacity overflow, empty input, or invalid options (version outside 1..40, ecLevel not one of L/M/Q/H) — it never throws.

Decode

import { decode } from "@elchika-inc/moonqr/decode";

// data is an RGBA pixel buffer (same shape as ImageData.data — Uint8Array or
// Uint8ClampedArray both work)
const result = decode(imageData.data, imageData.width, imageData.height);
if (result) {
  console.log(result.text, result.version, result.ecLevel, result.corners);
}

decode() returns null when no QR code is found or the input is invalid (e.g. data.length !== width * height * 4) — it never throws. options.invert (default true) controls whether an inverted-color QR code is also attempted automatically.

Render to canvas

import { toCanvas } from "@elchika-inc/moonqr/dom";
import { encode } from "@elchika-inc/moonqr/encode";

const matrix = encode("HELLO")!;
toCanvas(matrix, document.querySelector("canvas")!, { margin: 4, cell: 8 });

Subpath exports (bundle size)

If bundle size matters, import from a subpath instead of the package root (@elchika-inc/moonqr).

| Entry | Purpose | Size (CJS, minified) | |---|---|---| | @elchika-inc/moonqr/encode | Encode only | raw 24.4 KB / gzip 7.3 KB | | @elchika-inc/moonqr/decode | Decode only | raw 129.5 KB / gzip 49.8 KB | | @elchika-inc/moonqr/dom | toCanvas (canvas rendering) | raw 0.3 KB / gzip 0.2 KB (no MoonBit dependency) | | @elchika-inc/moonqr | Everything (convenience) | raw 154.0 KB / gzip 57.5 KB |

(Sizes are reproduced by node scripts/report-bundle-sizes.mjs at the repo root after pnpm -r build, and checked in CI on every push.)

The decoder is large (its Shift-JIS table alone accounts for a good chunk of it) and encode-only consumers should not have to bundle it. The encode subpath is a physically separate output file from decode, so it's excluded regardless of how good (or not) your downstream bundler's tree-shaking is.

The root entry also declares sideEffects: false, so a modern bundler can drop the unused half even when importing from the root — but that's not guaranteed. If size is a requirement, use the subpaths.

API reference

encode(text: string, options?: EncodeOptions): QrMatrix | null

interface EncodeOptions {
  ecLevel?: "L" | "M" | "Q" | "H"; // error correction level, default "M"
  version?: number;                // 1..40; omit to auto-pick the smallest that fits
}

interface QrMatrix {
  readonly size: number;
  get(x: number, y: number): boolean; // true = dark module. Out-of-range -> false, never throws.
}

toSvgString(matrix: QrMatrix, options?: SvgOptions): string

interface SvgOptions {
  margin?: number; // quiet-zone width in modules, default 4
  cell?: number;    // module side length in px, default 4
}

Pure function, no DOM dependency — works in Node.js too.

decode(data: Uint8Array | Uint8ClampedArray, width: number, height: number, options?: DecodeOptions): DecodeResult | null

interface DecodeOptions {
  invert?: boolean; // also try inverted color scheme, default true
}

interface DecodeResult {
  text: string;
  bytes: Uint8Array;                              // raw payload bytes (for binary payloads)
  version: number;
  ecLevel: "L" | "M" | "Q" | "H";
  corners: [Point, Point, Point, Point];          // source-image px, TL/TR/BR/BL order
}

interface Point { x: number; y: number; }

toCanvas(matrix: QrMatrix, canvas: HTMLCanvasElement, options?: SvgOptions): void

(@elchika-inc/moonqr/dom only.) Sets canvas.width/canvas.height to fit the matrix and draws it (white background, black modules), using the same margin/cell conventions as toSvgString.

Correctness

  • Decoder: 214/214 exact-text match against jsQR's own end-to-end test corpus (jsQR itself also gets 214/214 on the cases that have ground truth).
  • Encoder: bit-for-bit module match against the qrcode npm package across all 160 version (1–40) × error-correction-level (L/M/Q/H) combinations.

See bench/RESULT.md in the repo root for full methodology and performance numbers (decoding is faster than jsQR on both hit and miss frames).

Browser support

Ships as plain ESM + CJS with no bundler-specific syntax; targets ES2020. Works in any evergreen browser and in Node.js 18.18+. toCanvas (the /dom subpath) requires HTMLCanvasElement and its 2D context, i.e. a browser or a canvas-polyfilled environment — the encode and decode subpaths themselves have no DOM dependency and run fine in Node.js (see the parity/sweep tests under test/ for examples of decoding from a Node-side pixel buffer).

License and attribution

Apache License 2.0 — see LICENSE (also bundled in this package's published tarball).

Portions of the decoder are ported from jsQR (Apache-2.0), and the Reed–Solomon block / alignment-pattern position tables are derived from qrcode-generator (MIT). See NOTICE and THIRD_PARTY_LICENSES (also bundled) for the full attribution and upstream license texts.