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

@gueshbzh-perso/scanner

v0.9.0

Published

Fast QR code & barcode scanning for the web: camera, WebGL frame pipeline, Web Worker and Rust/WebAssembly decoder.

Readme

@gueshbzh-perso/scanner

Fast QR code & barcode scanning for the web. Rust/WebAssembly decoder, WebGL2 frame pipeline, parallel decoding in Web Workers, camera management and a ready-made overlay. No license key, no network calls: everything runs on the device.

⚠️ Work in progress: the API may still change before 1.0.

npm install @gueshbzh-perso/scanner

Camera scanning

<div id="scanner" style="width: 100%; aspect-ratio: 4 / 3"></div>
import { BarcodeReader } from '@gueshbzh-perso/scanner';

const reader = await BarcodeReader.create({
  host: '#scanner',
  formats: ['qr_code', 'ean_13', 'code_128'], // fewer formats = faster
});

reader.on('detected', (codes) => {
  console.log(codes[0].text, codes[0].format);
});

// Call it from a click handler: browsers require a user gesture for audio (iOS).
await reader.start();

// Later
await reader.stop();     // releases the camera, keeps the reader
await reader.destroy();  // removes the preview, frees the engine

Options

| Option | Default | Description | | --- | --- | --- | | host | required | Element or CSS selector. It must have a size; the preview fills it. | | formats | all | qr_code, micro_qr, data_matrix, aztec, pdf417, code_128, code_39, code_93, ean_13, ean_8, upc_a, upc_e, itf, codabar. | | regionOfInterest | { x: .1, y: .2, width: .8, height: .6 } | Part of the visible preview to scan (fractions of the host). Smaller = faster. | | duplicateIntervalMs | 1500 | Delay before the same content is reported again. | | maxResults | 1 | > 1 detects several codes per frame (slower). | | tryInvert | true | Also read light-on-dark codes. | | preprocessing | true | Contrast / binarization fallbacks for hard frames. | | linearConfirmations | 2 | Agreeing reads before a 1D code is reported. Weak 1D formats (Code 39/93, ITF, Codabar, UPC-E, EAN-8) need one more; 2D codes are reported on the first read. | | camera | { facingMode: 'environment', resolution: 'hd' } | Also deviceId, rememberDevice (default true), releaseWhenHidden (default true: the camera is released while the page is hidden and reopened when it is visible again). | | overlay | all on | showRegion, showDetections, showTorchButton, showCameraSwitch, color, or false. | | feedback | beep + vibrate | { beep, vibrate } or false. | | processing | { maxDimension: 1024, backend: 'auto' } | Decoder image size; 'webgl', 'canvas' or 'auto'. | | worker | true | Decode in Web Workers; falls back to the main thread if unavailable. | | workers | cores − 1 (1–3) | Number of decoding workers: frames are decoded in parallel. | | wasmUrl, workerUrl | auto | Override asset URLs (CDN, custom bundler setup). |

Reader API

  • start(), stop(), pause(), resume(), destroy()
  • on('detected' | 'error' | 'started' | 'stopped', listener) → returns an unsubscribe function
  • configure({ formats, duplicateIntervalMs, … }) — change decoding options live
  • setRegionOfInterest(region)
  • setTorch(on), torchAvailable, setZoom(zoom), switchCamera(deviceId?), BarcodeReader.listCameras()
  • clearCooldown(text) — allow a code to be reported again immediately
  • stats — { fps, decodeMs, grabMs, frameWidth, frameHeight, backend, worker, workers }
  • video — the underlying <video> element

Detection points are in video pixels (video.videoWidth space).

Scanning a still image

import { scanImage } from '@gueshbzh-perso/scanner';

const codes = await scanImage(file); // File/Blob, ImageData, ImageBitmap, <img>, <canvas>…

Still images go through every strategy (rotations, inversion, contrast…) and up to 8 codes are returned, with points in the source image's pixels.

Bundlers

The SDK loads its worker and WebAssembly binary with the standard new URL('./file', import.meta.url) pattern, supported by Vite, webpack 5, Rollup, esbuild-based tools and plain browser ES modules.

Vite / Nuxt: exclude the package from dependency pre-bundling, otherwise the asset URLs are rewritten to a folder where the files do not exist:

// vite.config.ts
export default { optimizeDeps: { exclude: ['@gueshbzh-perso/scanner'] } };

If your setup cannot resolve the assets, copy node_modules/@gueshbzh-perso/scanner/dist to your public folder and pass workerUrl / wasmUrl explicitly.

Content Security Policy

With a CSP, allow WebAssembly compilation and the SDK's module workers (served from your origin by your bundler):

script-src 'self' 'wasm-unsafe-eval';
worker-src 'self';

The camera needs camera allowed by Permissions-Policy (allowed by default for the top-level page; add allow="camera" to an <iframe>).

Low-level engine

createEngine() decodes frames you already have as pixels (custom pipelines, VideoFrames…), with the same worker pool as the reader. It is an advanced API: the Engine interface may gain members in minor versions.

How it works

camera ─▶ <video> ─▶ WebGL2: crop to ROI · RGB→luma · box-filter downscale · pack 4 px/texel
                         │  async readback (pixel-pack buffer + fence) — width×height bytes
                         ▼
   session (main thread) ─ plan ─▶ pool of Web Workers (parallel) ─▶ WASM memory ─▶ Rust decoder
   strategy scheduling                                             │ fast pass every frame
   multi-frame confirmation                                        │ + 1 fallback per frame (CPU-time share)
   de-dup                                                          │   (try-harder, inverted, CLAHE, Sauvola, Linear)
                         ◀────────── report (codes, timing) ───────┘ 1D quiet-zone check
                 new codes + live detections
overlay (ROI mask, highlights, torch / camera buttons) · beep · vibration
  • The grab of the next frame overlaps the decoding of the previous ones, and frames are decoded by several workers at once. When every worker is busy, frames are skipped instead of queued, so latency never builds up.
  • The scan state lives in one place (main thread), so multi-frame confirmation and de-duplication work across workers.
  • If WebGL2 is missing or the context is lost, the pipeline switches to Canvas 2D.
  • If the engine crashes, the worker is restarted transparently.
  • The engine is downloaded and compiled once, then shared by every worker.

Browser support

Any browser with WebAssembly, getUserMedia and ES modules in workers: Chrome/Edge 80+, Firefox 114+, Safari 15+. Camera access requires HTTPS (or localhost).

Development

This package is part of an npm workspace: run commands from the repository root (see the root README). A Nuxt 4 demo lives in apps/demo-nuxt.

npm install        # at the repository root
npm run build      # Rust → WASM, then TypeScript
npm run test:e2e   # headless Chromium with a fake camera (needs ffmpeg + Playwright)

npm run build:wasm only needs Rust (rustup + cargo): the wasm32-unknown-unknown target and the wasm-bindgen-cli version pinned in Cargo.lock are installed automatically on first run; wasm-opt comes from the binaryen devDependency.

License

MIT. The WebAssembly engine is compiled from Rust crates under permissive licenses: see dist/wasm/THIRD_PARTY_LICENSES.md in the package.