microscope-js - v0.0.0
    Preparing search index...

    microscope-js - v0.0.0

    🔬 microscope-js

    Render PDF · Word · Excel · PowerPoint · image · video · audio — entirely in the browser. No Microsoft Online Viewer iframe. No Google Docs preview. No "we upload your file to our server first." The bytes never leave the tab.

    CI Release Pages CodeQL

    npm Downloads Bundle Tree-shakable Types Provenance Trusted Publisher

    License Stars Forks Last commit Issues PRs welcome

    TypeScript pnpm Changesets Biome Vitest

    🌐 Live demo · 📚 API docs · 🐛 Issues · 💬 Discussions


    • 🔒 Zero data exfiltration. Documents are parsed in the user's browser. No backend round-trip, no third-party iframe, no telemetry.
    • 🪶 Pay for what you use. Every format lives in its own npm package; tree-shake or dynamic-import only what your app actually needs.
    • ⚛️ React & Next.js native. 'use client', drop in <Viewer />, done. The hook works with Suspense and the App Router.
    • 🧩 Pluggable. A renderer is 30 lines of TypeScript implementing one interface. Add a new format in an afternoon.
    • 🛡️ Hardened by default. DOMPurify-sanitized HTML, zip-slip & zip-bomb defenses, size caps, no eval, no dangerouslySetInnerHTML of user content.
    • 🔏 Signed releases. Every npm version ships with SLSA provenance via GitHub OIDC trusted publishing — verifiable build origin, no tokens involved.

    👉 shubham8550.github.io/microscope-js

    Drop any of these onto the page — it renders locally:

    • 📄 *.pdf
    • 📝 *.docx   *.doc
    • 📊 *.xlsx   *.xls   *.csv   *.tsv   *.ods
    • 📽️ *.pptx
    • 🖼️ *.png *.jpg *.gif *.webp *.svg *.avif *.bmp
    • 🎞️ *.mp4 *.webm *.ogg *.mov *.mkv
    • 🎵 *.mp3 *.wav *.flac *.aac *.m4a *.opus
    • 💻 *.txt *.md *.json *.yaml and every common source-code extension
    Package Version Min+gzip Description
    @microscope-js/core npm size Framework-agnostic registry + mount()
    @microscope-js/utils npm size Source / MIME / sanitize helpers
    @microscope-js/react npm size React / Next.js adapter, <Viewer />
    @microscope-js/renderer-pdf npm size PDF via pdfjs-dist
    @microscope-js/renderer-docx npm size Word .docx via mammoth + DOMPurify
    @microscope-js/renderer-xlsx npm size Excel .xlsx/.xls/.csv via SheetJS
    @microscope-js/renderer-pptx npm size PowerPoint .pptx via JSZip
    @microscope-js/renderer-image npm size png / jpg / gif / webp / svg / avif / bmp
    @microscope-js/renderer-video npm size mp4 / webm / ogg / mov
    @microscope-js/renderer-audio npm size mp3 / wav / flac / ogg / aac
    @microscope-js/renderer-text npm size Plain text / source code
    pnpm add @microscope-js/react @microscope-js/renderer-pdf @microscope-js/renderer-image
    
    'use client';
    import { Viewer, useRegistry } from '@microscope-js/react';
    import { pdfRenderer } from '@microscope-js/renderer-pdf';
    import { imageRenderer } from '@microscope-js/renderer-image';

    export default function FilePreview({ file }: { file: File }) {
    const registry = useRegistry([pdfRenderer, imageRenderer]);
    return <Viewer source={file} registry={registry} style={{ height: 600 }} />;
    }

    <Viewer /> accepts a File, Blob, ArrayBuffer, Uint8Array, URL, or a string URL — anything you have on hand.

    import { createRegistry, mount } from '@microscope-js/core';
    import { pdfRenderer } from '@microscope-js/renderer-pdf';

    const registry = createRegistry([pdfRenderer]);
    const handle = await mount({
    source: fileInput.files[0],
    container: document.getElementById('viewer')!,
    registry,
    });
    // later...
    handle.destroy();
    const { containerRef, loading, error, handle } = useViewer({ source, registry });
    return (
    <div ref={containerRef} className="my-toolbar-frame">
    {handle?.capabilities?.pageCount /* renderer-specific extras */}
    </div>
    );
    Concern MS Office iframe Google Docs viewer docx-preview / etc. microscope-js
    File leaves the device ✅ uploads to Microsoft ✅ uploads to Google ❌ ❌
    Works offline ❌ ❌ partial ✅
    Formats covered Office only Office + PDF one per library PDF + Office + media + image + code, one API
    Bundle cost iframe (heavy) iframe (heavy) one library, monolithic ~1–5 KB core + per-format packages, tree-shakable
    React + Next.js / SSR ⚠️ iframe ⚠️ iframe varies ✅ first-class, SSR-safe
    Provenance / signing n/a n/a usually unsigned ✅ SLSA + npm trusted publisher
    License proprietary proprietary mixed MIT
                    ┌─────────────────────────────────┐
    │ @microscope-js/core │
    │ Renderer interface + registry │
    └──────────────┬──────────────────┘
    │
    ┌───────────────────────┼──────────────────────────┐
    │ │ │
    ┌──────▼──────┐ ┌───────▼────────┐ ┌───────▼─────────┐
    │ renderer-pdf│ ... │ renderer-image │ ... │ renderer-docx │
    └─────────────┘ └────────────────┘ └─────────────────┘
    │ │ │
    └───────────────────────┼──────────────────────────┘
    │
    ┌──────────────▼──────────────────┐
    │ @microscope-js/react │
    │ <Viewer />, useViewer() │
    └─────────────────────────────────┘

    The core package defines exactly one interface (Renderer) and one registry. Every format implementation lives in its own npm package and is loaded via dynamic import() so first-paint pays only for core + react.

    Threat Mitigation
    HTML in Office documents All renderer output passes through DOMPurify with a hardened allowlist
    Zip-slip (.. in DOCX/XLSX/PPTX) Archive entries with parent-dir or absolute paths are rejected before extraction
    Zip bombs ByteBudget caps total uncompressed bytes (default 512 MB)
    Oversized PDFs Configurable maxBytes / maxPages per renderer
    SVG / XML XSS SVGs are mounted via <img> (no script execution); inline SVG is sanitized
    Dynamic JS No eval, no new Function, no dangerouslySetInnerHTML of user content — CI greps for these
    Supply-chain Every published tarball is signed with SLSA provenance via npm OIDC trusted publishing

    See SECURITY.md to report a vulnerability privately.

    pnpm install
    pnpm build # build all packages
    pnpm test # vitest across the monorepo
    pnpm typecheck # tsc --noEmit, per package
    pnpm lint # biome check
    pnpm demo # localhost:3000
    pnpm docs # TypeDoc -> ./docs-site
    microscope-js/
    ├── packages/
    │ ├── core/ # Renderer interface + registry
    │ ├── utils/ # source / sniff / sanitize / zip-safety helpers
    │ ├── react/ # <Viewer /> + useViewer + useRegistry
    │ ├── renderer-pdf/
    │ ├── renderer-docx/
    │ ├── renderer-xlsx/
    │ ├── renderer-pptx/
    │ ├── renderer-image/
    │ ├── renderer-video/
    │ ├── renderer-audio/
    │ └── renderer-text/
    ├── apps/
    │ └── demo/ # Next.js → GitHub Pages
    └── .github/workflows/ # CI · release · pages · security · dependabot
    • [ ] Pixel-perfect PPTX (shape + image layout, not just text)
    • [ ] EPUB / FB2 ebook renderer
    • [ ] RTF / ODT renderer
    • [ ] Optional Web Worker for office formats (offload main-thread parsing)
    • [ ] Plugin: in-viewer search across PDF / DOCX
    • [ ] Plugin: annotation / highlight overlay
    • [ ] Solid / Vue / Svelte adapters

    Got an idea? Open a discussion.

    PRs are welcome — see CONTRIBUTING.md. Adding a new format is a copy of packages/renderer-image with a new Renderer object. The registry picks it up automatically.

    If this saved you a backend, a star on GitHub goes a long way. ⭐

    Star history chart

    MIT © microscope-js contributors

    Built with pnpm · tsup · Changesets · Biome · Vitest · TypeDoc. Published with npm Trusted Publishers + SLSA provenance.