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

@pagemirror/snapshot-core

v0.1.0

Published

Framework-agnostic core for Page Mirror snapshot bundles: browser collector, HTML assembler, manifest builder, save orchestration.

Readme

@pagemirror/snapshot-core

Framework-agnostic core for building Page Mirror snapshot bundles. This package contains no driver-specific code — it's the shared engine that Playwright, Selenium, Cypress, and Appium adapters all build on.

If you just want to capture Playwright snapshots, use playwright-snapshot-saver instead. Reach for this package when you're:

  • Writing a new driver adapter for a framework Page Mirror doesn't support yet.
  • Building a custom reporter that post-processes captured pages.
  • Consuming raw Playwright trace data and want the rendering pipeline without the Playwright-specific glue.

Installation

npm install @pagemirror/snapshot-core

What this package provides

A small set of pure functions plus a couple of interfaces. Everything is stateless; you orchestrate.

Live capture

  1. collectorSource / collectPage — browser-side collector that serializes document.documentElement, pulls in every <link rel="stylesheet"> + <style>, and returns a CollectedPayload.
  2. assembleHtml(captured) — rewrites the collected HTML to reference CSS sidecars under resources/<sha1>.css.
  3. buildManifest(captured, driver?) — produces a schema-v2 manifest.json object.
  4. saveSnapshot(adapter, options) — orchestrator that writes the full bundle to disk. Takes a PageAdapter you implement.

Trace pipeline (framework-agnostic)

  1. TraceBackend — the single interface you implement to give core access to your driver's trace data (frame snapshots, resources, screencast frames, resource-by-sha1).
  2. renderSnapshot(backend, pageOrFrameId, snapshotName) — pure renderer that turns a FrameSnapshot plus its resource overrides into HTML.
  3. inlineResources(rendered) — resolves every <link>, <img>, CSS url(...), @font-face, and SVG <use> reference against the backend's sha1 store, emitting files under resources/ and rewriting the HTML to match. Strips any <base> element.
  4. extractFromBackend(backend, markers, options) — orchestrator that takes a list of TraceMarkers and writes one v2 bundle per marker.

Writing a new driver adapter

A driver adapter is a class that implements PageAdapter. It has one job: run the collector inside a live page and hand core a CapturedPage.

import {
  PageAdapter,
  CaptureRequest,
  CapturedPage,
  collectorSource,
} from '@pagemirror/snapshot-core';

export class MyDriverAdapter implements PageAdapter {
  constructor(private readonly page: MyDriverPage) {}

  async capture(request: CaptureRequest): Promise<CapturedPage> {
    // 1. Run `collectorSource` inside the page. Every adapter ships the
    //    same stringified source so the collector behaves identically
    //    everywhere. Most drivers need to eval the source inside the
    //    page rather than passing the function directly — serializers
    //    often mangle nested async function bodies.
    const payload = await this.page.evaluate(
      async ({ src, opts }) => {
        // eslint-disable-next-line no-eval
        const fn = eval(`(${src})`) as (o: unknown) => Promise<unknown>;
        return fn(opts);
      },
      {
        src: collectorSource,
        opts: {
          extraSelectors: request.extraSelectors,
          excludeSelectors: request.excludeSelectors,
          extraAttributes: request.extraAttributes,
        },
      },
    );

    // 2. Fill in driver-specific metadata.
    const captured: CapturedPage = {
      html: payload.html,
      stylesheets: payload.stylesheets,
      resources: [],
      url: this.page.url(),
      viewport: await this.page.viewportSize(),
      userAgent: await this.page.userAgent(),
    };

    // 3. Optional screenshot — push it into `resources` with the final filename.
    if (request.screenshot) {
      const bytes = await this.page.screenshot({
        format: request.screenshot.format,
        fullPage: request.screenshot.fullPage,
      });
      captured.resources.push({
        filename: `screenshot.${request.screenshot.format}`,
        bytes,
      });
    }

    return captured;
  }
}

Consumers then call saveSnapshot:

import { saveSnapshot } from '@pagemirror/snapshot-core';

await saveSnapshot(new MyDriverAdapter(page), {
  outputDir: '.snapshots',
  name: 'initial',
  group: 'login',
  driver: { name: 'my-driver', version: '1.2.3' },
});

The driver.name becomes the key in manifest.json — e.g. { "my-driver": "1.2.3" } — so downstream tooling can distinguish bundles by producer.

Writing a new trace backend

A trace backend lets you reuse the entire extraction pipeline with any trace format. Implement TraceBackend:

import { TraceBackend, FrameSnapshot, ResourceEntry, ScreencastFrame } from '@pagemirror/snapshot-core';

export class MyTraceBackend implements TraceBackend {
  getFrameSnapshots(pageOrFrameId: string): FrameSnapshot[] {
    // Return frame snapshots in trace order. Shape matches Playwright's
    // internal snapshot format — see docs on `FrameSnapshot`.
  }

  getResources(): ResourceEntry[] {
    // All network-resource entries, sorted by monotonicTime ascending.
  }

  getScreencastFrames(pageId: string): ScreencastFrame[] {
    // Optional — omit if your trace format has no screencast.
  }

  async readResource(sha1: string): Promise<Uint8Array | undefined> {
    // The single I/O primitive. Return bytes for the given sha1, or
    // undefined when unknown.
  }
}

Then extract:

import { extractFromBackend, TraceMarker } from '@pagemirror/snapshot-core';

const markers: TraceMarker[] = [
  {
    page: 'login',
    state: 'initial',
    pageOrFrameId: 'page@abc123',
    snapshotName: 'after@call42',
    timestamp: 1.234,
    wallTime: 1705312345678,
  },
];

const result = await extractFromBackend(backend, markers, {
  outputDir: '.snapshots',
  screenshot: true,          // emits resources/screenshot.webp when available
  manifest: true,
  driver: { name: 'my-driver', version: '1.2.3' },
});

for (const info of result.snapshots) {
  console.log(`${info.page}/${info.state} -> ${info.outputDir}`);
}

extractFromBackend is the only orchestrator you need. It calls renderSnapshot and inlineResources internally, writes the v2 bundle layout to disk, and returns the paths.

Bundle format (v2)

<name>/
  index.html                   # Sanitized DOM referencing resources/
  manifest.json                # Schema version 2
  resources/
    screenshot.png|webp        # Optional
    <sha1>.css                 # Stylesheet sidecars
    <sha1>.woff2|png|jpg|…     # Fonts, images, media (trace-extracted bundles)

manifest.json:

{
  "version": 2,
  "url": "https://example.com/login",
  "viewport": { "width": 1280, "height": 720 },
  "timestamp": "2025-01-15T10:30:00Z",
  "userAgent": "Mozilla/5.0 …",
  "playwright": "1.58.0"
}

Exactly one driver field is populated per manifest, matching DriverInfo.name. The v1 format is not backwards compatible and is refused by the plugin.

See docs/snapshot-bundle-spec.md in the main repo for the authoritative spec.

Public API reference

Types

| Type | Purpose | |----------------------------|----------------------------------------------------------------------------------| | StylesheetData | One stylesheet (href?, source) as produced by the collector | | Resource | A file to be written under resources/ (filename, bytes) | | CapturedPage | Output of a PageAdapter.capture() call | | CollectorOptions | extraSelectors / excludeSelectors / extraAttributes | | ScreenshotOptions | { format: 'png' \| 'webp', fullPage: boolean } | | CaptureRequest | CollectorOptions & { screenshot?: ScreenshotOptions } | | PageAdapter | The driver abstraction — capture(request): Promise<CapturedPage> | | DriverInfo | { name, version } written into the manifest | | SaveSnapshotOptions | Options for saveSnapshot | | SnapshotResult | { outputDir, files: { html, manifest?, resources } } | | ManifestJson | Schema-v2 manifest shape | | MANIFEST_VERSION | Constant 2 |

Trace types

| Type | Purpose | |----------------------------|----------------------------------------------------------------------------------| | NodeSnapshot | Recursive DOM tuple layout matching Playwright's internal format | | FrameSnapshot | One captured DOM state for a frame | | ResourceEntry | Network-resource log entry | | ResourceOverride | Per-snapshot URL → sha1 override | | ScreencastFrame | One screencast frame (sha1 + timestamp) | | TraceBackend | The trace-data abstraction you implement | | TraceMarker | Snapshot marker consumed by extractFromBackend | | ExtractFromBackendOptions| Options for extractFromBackend | | ExtractFromBackendResult | { snapshots: ExtractedSnapshotInfo[] } |

Functions

| Function | Description | |---------------------------|-----------------------------------------------------------------------------------| | collectPage(opts) | Runs the browser-side collector. Call inside the page. | | collectorSource | Stringified version of collectPage, suitable for page.evaluate(source, opts). | | assembleHtml(captured) | Rewrites captured HTML, returns { html, cssResources }. | | buildManifest(captured, driver?, now?) | Produces a v2 ManifestJson. | | saveSnapshot(adapter, options) | Full live-capture orchestrator. | | renderSnapshot(backend, pageOrFrameId, snapshotName) | Renders a single FrameSnapshot to HTML. | | inlineResources(rendered) | Resolves all resource references, emitting files under resources/. | | extractFromBackend(backend, markers, options) | Full trace-extraction orchestrator. | | extensionFromContentType(mimeType) | Maps a MIME type to a canonical file extension. |

Requirements

  • Node.js 18+

License

MIT