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

wascanner

v0.3.0

Published

WebAssembly-powered document scanner — a WASM (Go) reimagining of jscanify, as a TypeScript library and a Vue component.

Readme

wascanner

WebAssembly-powered document scanner — a WASM (Go) reimagining of jscanify.

wascanner detects a document (a sheet of paper, receipt, card, …) in an image or camera frame and can perspective-correct ("dewarp") it into a flat scan. The web layer only handles the camera and drawing the overlay — all image analysis runs in a compact Go/WebAssembly binary for speed and portability.

It ships in two forms:

  • wascanner — a framework-agnostic TypeScript/JS library.
  • wascanner/vue — a <DocumentScanner> Vue 3 component with a built-in camera + live overlay, importable straight into a Vite app.

How it works

 camera / <img> / <canvas>          ── JS (this library) ──▶  RGBA pixels
                                                                   │
                                                                   ▼
   overlay + dewarped scan  ◀── JS ──   corners / warped image   WASM (Go)

The Go core (wasm/) runs the classic scanning pipeline entirely in WebAssembly:

grayscale → downscale → gaussian blur → Otsu threshold → morphological close → connected components → largest blob → extreme-point corner estimation → perspective dewarp

No native/OpenCV dependency — pure Go, cross-compiled to js/wasm.

Install

npm install wascanner
# peer (only needed for the Vue component):
npm install vue

The package ships the prebuilt dist/wascanner.wasm and dist/wasm_exec.js.

Quick start — core library

import { WAScanner } from 'wascanner';

const scanner = await WAScanner.load();

// Any ImageData | <canvas> | <img> | <video> | ImageBitmap source:
const result = scanner.detect(imageEl);
if (result.found) {
  console.log(result.corners); // { tl, tr, br, bl }
  const overlay = scanner.highlight(imageEl);   // -> HTMLCanvasElement
  const flat = scanner.extract(imageEl);        // -> dewarped HTMLCanvasElement
  document.body.append(overlay, flat);
}

Off the main thread (recommended for live camera)

WAScannerWorker mirrors WAScanner with async methods and runs all WASM analysis in a Web Worker, so detection never stalls the camera preview or UI:

import { WAScannerWorker } from 'wascanner';

const scanner = await WAScannerWorker.load();
const result = await scanner.detect(videoEl);  // Promise<DetectResult>
const flat = await scanner.extract(videoEl);   // Promise<HTMLCanvasElement>
scanner.dispose();                             // terminates the worker

The Vue <DocumentScanner> uses the worker automatically when Workers are available, falling back to in-thread analysis otherwise.

With Vite / explicit asset URLs

WAScanner.load() resolves the .wasm next to its own module, which works when the package is served as-is. In a bundled app the most reliable path is to pass the URLs explicitly using Vite's ?url imports:

import wasmUrl from 'wascanner/wasm?url';
import wasmExecUrl from 'wascanner/wasm-exec?url';
import workerUrl from 'wascanner/worker?url'; // worker variant only
import { WAScanner, WAScannerWorker } from 'wascanner';

const scanner = await WAScanner.load({ wasmUrl, wasmExecUrl });
// or, worker-backed (dist/worker.js is self-contained, safe to copy as an asset):
const workerScanner = await WAScannerWorker.load({ workerUrl, wasmUrl, wasmExecUrl });

Quick start — Vue component

<script setup lang="ts">
import { ref } from 'vue';
import { DocumentScanner } from 'wascanner/vue';
import 'wascanner/style.css'; // optional cosmetic styles

const scanner = ref();
const shot = ref('');
</script>

<template>
  <DocumentScanner
    ref="scanner"
    facing-mode="environment"
    :options="{ color: '#22c55e', fill: 'rgba(34,197,94,0.15)' }"
    @capture="({ dataUrl }) => (shot = dataUrl)"
  />
  <button @click="scanner.capture()">Capture</button>
  <img v-if="shot" :src="shot" />
</template>

Props: facingMode, constraints, autostart, options (processMaxDim, minAreaRatio, invert, color, lineWidth, fill, handles), interval (ms between detections, default 200), smoothing (overlay easing time constant in ms, default 180; the overlay animates every frame and eases toward the latest detection), hold (ms to keep the last quad through missed detections, default 800), wasm. Events: ready, detect(result), capture({ canvas, dataUrl }), error. Exposed methods: start(), stop(), capture().

API

| Method | Returns | Notes | | --- | --- | --- | | WAScanner.load(assets?) | Promise<WAScanner> | Loads + caches the WASM core (in-thread). | | .detect(source, opts?) | DetectResult | { found, score, corners, width, height }. | | .highlight(source, opts?) | HTMLCanvasElement | Source with the detected outline drawn. | | .extract(source, corners?, w?, h?) | HTMLCanvasElement | Perspective-corrected document. | | WAScannerWorker.load(assets?) | Promise<WAScannerWorker> | Same pipeline in a Web Worker. | | .detect(source, opts?) | Promise<DetectResult> | Async; pixels are transferred, not copied. | | .extract(source, corners?, w?, h?) | Promise<HTMLCanvasElement> | Detects first when corners omitted. | | .dispose() | void | Terminates the worker. |

DetectOptions: processMaxDim (default 480), minAreaRatio (default 0.12), invert (for dark-on-light documents).

Building from source

compile.sh checks for the Go and Node toolchains, installs npm deps, and builds into dist/:

./compile.sh            # build both WASM + JS  (alias: --all)
./compile.sh --wasm     # only the Go WebAssembly binary (+ wasm_exec.js)
./compile.sh --js       # only the TS/Vue library (Vite)
./compile.sh --deps     # just check/install dependencies
./compile.sh --clean    # remove dist/

Requires Go ≥ 1.21 and Node ≥ 18.

Example app

./compile.sh --all
npm run example
# core demo → https://localhost:5173/
# vue  demo → https://localhost:5173/vue.html

The example is a zero-dependency Node static server showcasing image upload, a synthetic sample, live camera detection, and document extraction. It listens on all interfaces and serves over HTTPS by default (auto-generating a self-signed cert covering localhost and your LAN IPs) so the camera works both locally and from other devices — accept the one-time browser warning. Serve plain http with HTTPS=0 npm run example.

Tests

npm test            # runs the Go pipeline unit tests

Notes & limitations

  • Detection runs three complementary segmentations and picks the best-scoring quad: color distance from the background (handles a neutral page on a cream/wood surface where brightness barely differs), an edge-enclosed region (finds the page from its boundary/shadow regardless of contrast), and a classic brightness blob (fast path for a page on a darker surface). It still assumes a single, roughly rectangular document that is the most prominent object — not arbitrary scenes. For Apple-VNDocument-grade robustness on cluttered backgrounds you'd want an ML detector (a natural future addition behind the same API).
  • Camera access requires a secure context (https:// or localhost).

License

MIT © andor83