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

api-diff-viewer

v1.0.0

Published

CodeMirror-based component to view the difference between two documents: OpenAPI, AsyncAPI, JSON Schema

Readme

api-diff-viewer

CodeMirror-based diff viewer for JSON-based API documents. Supports OpenAPI 3.x, AsyncAPI 2.x, and JsonSchema specifications.

Storybook

Features

  • Side-by-side & inline views — dual-editor or unified diff display
  • YAML & JSON output — render diffs in either format
  • Word-level diff highlighting — word or character granularity, or disable entirely
  • Change classification — breaking, non-breaking, annotation, unclassified with color-coded gutter indicators
  • Change filtering — show only specific classification types
  • Code folding — collapse/expand API structure blocks with change count badges
  • Array-level diffs — per-item change tracking for parameters, enum values, tags, etc.
  • Dark mode — built-in light and dark themes with CSS variable overrides
  • WebWorker support — non-blocking merge computation for large specs
  • Event system — lifecycle events for loading, ready, errors, and state changes
  • Zero framework dependency — pure DOM + CodeMirror, works with any framework or vanilla JS

Installation

npm install api-diff-viewer

Quick Start

import { createDiffViewer } from 'api-diff-viewer'
import 'api-diff-viewer/style.css'

const viewer = createDiffViewer(
  document.getElementById('diff')!,
  beforeSpec, // object or JSON/YAML string
  afterSpec,
  { format: 'yaml', mode: 'side-by-side' }
)

viewer.on('ready', ({ summary }) => {
  console.log(`${summary.total} changes (${summary.breaking} breaking)`)
})

// Cleanup when done
viewer.destroy()

API Reference

createDiffViewer(container, before, after, options?)

Creates and returns a DiffViewer instance.

| Parameter | Type | Description | | ----------- | ------------------- | -------------------------------------------------- | | container | HTMLElement | DOM element to mount into | | before | object \| string | The "before" API spec (object or JSON/YAML string) | | after | object \| string | The "after" API spec (object or JSON/YAML string) | | options | DiffViewerOptions | Configuration (see below) |

DiffViewerOptions

| Option | Type | Default | Description | | --------------------------- | ---------------------------- | ---------------- | -------------------------------------------------------- | | mode | 'side-by-side' \| 'inline' | 'side-by-side' | Display mode | | format | 'json' \| 'yaml' | 'yaml' | Output format | | filters | DiffType[] | [] | Active diff type filters (empty = show all) | | dark | boolean | false | Enable dark theme | | theme | Extension | — | Base CodeMirror theme extension | | colors | Partial<DiffThemeColors> | {} | Diff-specific color overrides via CSS variables | | enableFolding | boolean | false | Enable code folding for API blocks | | showClassification | boolean | false | Show classification gutter bars and fold badges | | wordDiffMode | 'word' \| 'char' \| 'none' | 'word' | Word-level diff granularity | | wordWrap | boolean | true | Enable word wrapping (false adds synced horizontal scroll) | | useWorker | boolean | true | Use WebWorker for merging (non-blocking) | | workerUrl | string | '' | Custom worker URL (default: inline blob) | | mergeOptions | CompareOptions | {} | Override options passed to api-smart-diff |

DiffViewer Instance Methods

Display Controls

viewer.setMode('inline')           // Switch between 'side-by-side' and 'inline'
viewer.getMode()                   // Returns current mode

viewer.setFormat('json')           // Switch between 'json' and 'yaml'
viewer.getFormat()                 // Returns current format

viewer.setFilters(['breaking'])    // Filter visible changes by classification
viewer.getFilters()                // Returns active filters

viewer.setTheme({ dark: true })    // Toggle dark mode
viewer.setTheme({ colors: { ... }}) // Override diff colors
viewer.isDark()                    // Returns dark mode state

viewer.setWordDiffMode('char')     // Change word diff granularity
viewer.setWordWrap(false)          // Disable word wrap (enables synced horizontal scroll)
viewer.getWordWrap()               // Returns word wrap state
viewer.setFoldingEnabled(true)     // Toggle code folding
viewer.setClassificationEnabled(true) // Toggle classification indicators

Data Updates

viewer.update(newBefore, newAfter) // Replace specs and re-render

Fold Control

viewer.expandAll()                 // Expand all folded blocks
viewer.collapseAll()               // Collapse all foldable blocks

Events

viewer.on('loading', () => { /* merge started */ })

viewer.on('ready', ({ summary }) => {
  console.log(`${summary.total} changes`)
  console.log(`${summary.breaking} breaking`)
  console.log(`${summary.nonBreaking} non-breaking`)
  console.log(`${summary.annotation} annotation`)
  console.log(`${summary.unclassified} unclassified`)
})

viewer.on('error', ({ message, cause }) => {
  console.error('Merge failed:', message)
})

viewer.on('modeChange', ({ mode }) => { /* 'side-by-side' | 'inline' */ })
viewer.on('formatChange', ({ format }) => { /* 'json' | 'yaml' */ })
viewer.on('themeChange', ({ dark }) => { /* boolean */ })
viewer.on('wordWrapChange', ({ wordWrap }) => { /* boolean */ })

Advanced

// Access underlying CodeMirror editors
const { before, after } = viewer.getEditorViews()     // side-by-side
const { unified } = viewer.getEditorViews()            // inline

// Get change summary without navigation
viewer.getChangeSummary()

// Cleanup
viewer.destroy()

DiffThemeColors

CSS variable overrides for diff colors:

viewer.setTheme({
  colors: {
    addedBg: 'rgba(46, 160, 67, 0.15)',
    removedBg: 'rgba(248, 81, 73, 0.15)',
    modifiedBg: 'rgba(227, 179, 65, 0.15)',
    breakingColor: '#cf222e',
    nonBreakingColor: '#1a7f37',
    annotationColor: '#8250df',
    unclassifiedColor: '#656d76',
    addedTextBg: 'rgba(46, 160, 67, 0.4)',
    removedTextBg: 'rgba(248, 81, 73, 0.4)',
    spacerBg: '#f6f8fa',
    spacerStripe: '#e1e4e8',
  }
})

ChangeSummary

Returned by ready event and getChangeSummary():

interface ChangeSummary {
  total: number
  breaking: number
  nonBreaking: number
  annotation: number
  unclassified: number
  byPath: Map<string, { type: DiffType; count: number }[]>
}

Diff Engine

The diff logic is powered by api-smart-diff, which performs structural merging and semantic comparison of API specifications. It understands the semantics of OpenAPI, AsyncAPI, and JsonSchema documents — so changes are classified not just as text edits but by their impact on API consumers.

The merge pipeline:

  1. Structural mergeapi-smart-diff walks both specs in parallel, producing a single merged document annotated with $diff metadata on every changed node. Each $diff entry carries an action (add, remove, replace, rename) and a type (breaking, non-breaking, annotation, unclassified).
  2. Diff tree construction — the merged document is converted into a DiffBlockData tree by the diff builder, which generates format-specific tokens (JSON or YAML syntax) for rendering.
  3. Content alignment — for side-by-side mode, spacer lines are inserted so corresponding sections align visually across both editors.

You can pass options through to api-smart-diff via the mergeOptions field:

createDiffViewer(container, before, after, {
  mergeOptions: {
    // Custom comparison rules
    rules: { ... },
    // Custom annotation hook
    annotateHook: (before, after, ctx) => { ... },
    // Resolved external $ref sources
    externalSources: {
      before: { 'common.yaml': { ... } },
      after: { 'common.yaml': { ... } },
    },
  },
})

The mergeOptions field accepts api-smart-diff options. Available parameters:

| Option | Type | Description | | ----------------- | ------------------------------------------------- | ---------------------------------------- | | rules | CompareRules | Custom comparison rules | | annotateHook | AnnotateHook | Custom hook for change annotations | | externalSources | { before?: Record<string, unknown>; after?: Record<string, unknown> } | Resolved external $ref sources |

Note: metaKey and arrayMeta are managed internally and cannot be overridden.

Change Classification Types

| Type | Description | Examples | | -------------- | ------------------------------------ | ------------------------------------------------------------------------------- | | breaking | Changes that break existing clients | Removing endpoints/parameters, changing types, adding required fields | | non-breaking | Backwards-compatible changes | Adding optional parameters/properties, relaxing constraints, adding enum values | | annotation | Documentation-only changes | Description, summary, example changes | | unclassified | Changes without clear classification | Extension field (x-*) changes |

Usage Examples

Basic Side-by-Side

import { createDiffViewer } from 'api-diff-viewer'
import 'api-diff-viewer/style.css'

const viewer = createDiffViewer(
  document.getElementById('diff')!,
  openApiV1,
  openApiV2,
)

Inline Mode with Folding

const viewer = createDiffViewer(container, before, after, {
  mode: 'inline',
  enableFolding: true,
  showClassification: true,
})

Dark Mode with Custom Colors

const viewer = createDiffViewer(container, before, after, {
  dark: true,
  colors: {
    breakingColor: '#ff6b6b',
    nonBreakingColor: '#51cf66',
  },
})

Filtering Breaking Changes

const viewer = createDiffViewer(container, before, after, {
  filters: ['breaking'],
  enableFolding: true,
  showClassification: true,
})

Synchronous Mode (No Worker)

const viewer = createDiffViewer(container, before, after, {
  useWorker: false,
})
// Ready immediately after construction (no 'loading' event)

Runtime Option Changes

const viewer = createDiffViewer(container, before, after)

// Toggle mode
document.getElementById('toggle-mode')!.onclick = () => {
  viewer.setMode(viewer.getMode() === 'side-by-side' ? 'inline' : 'side-by-side')
}

// Toggle format
document.getElementById('toggle-format')!.onclick = () => {
  viewer.setFormat(viewer.getFormat() === 'yaml' ? 'json' : 'yaml')
}

// Toggle dark mode
document.getElementById('toggle-dark')!.onclick = () => {
  viewer.setTheme({ dark: !viewer.isDark() })
}

Updating Specs

const viewer = createDiffViewer(container, specV1, specV2)

// Later, compare different versions
viewer.update(specV2, specV3)

Development

pnpm install          # Install dependencies
pnpm run build        # Type-check + build library
pnpm run storybook    # Run Storybook on port 6006
pnpm run demo         # Run demo on port 5173
pnpm run test         # Run tests
pnpm run lint         # Lint with Biome

License

MIT