Neo Zero

diff

Pool

Modern, tree-shakeable diff library - 70% smaller than diff, multiple algorithms, TypeScript-native

$ lpm install @lpm.dev/neo.diff

@lpm.dev/neo.diff

Modern, fast, and tree-shakeable diff library for JavaScript & TypeScript

Features

  • ๐Ÿš€ Fast: 1.16M ops/sec for small text, 811K ops/sec for objects
  • ๐Ÿ“ฆ Tiny: 12 KB minified, 3.99 KB gzipped (86% smaller than diff package)
  • ๐ŸŒณ Tree-shakeable: Import only what you need (~50-200 bytes per function)
  • ๐ŸŽฏ TypeScript-first: Full type safety with strict mode
  • ๐Ÿ”‹ Zero dependencies: No runtime dependencies
  • ๐ŸŽจ Multiple algorithms: Myers (fast) and Patience (readable)
  • ๐Ÿ“ Text diffing: Lines, words, characters, sentences
  • ๐Ÿ—๏ธ Structured diffing: JSON, arrays, objects (microdiff-style)
  • ๐Ÿฉน Patch support: Generate, apply, and parse unified diff patches
  • โšก Modern: ES2022, dual ESM/CJS builds

Installation

lpm install @lpm.dev/neo.diff

Quick Start

Text Diffing

import { diffLines, diffWords, diffChars } from "@lpm.dev/neo.diff";

// Line-by-line diff (most common)
const lineDiff = diffLines(
  "line 1\nline 2\nline 3",
  "line 1\nmodified\nline 3",
);
// => [
//   { value: 'line 1\n', count: 1 },
//   { value: 'line 2', removed: true, count: 1 },
//   { value: 'modified', added: true, count: 1 },
//   { value: '\nline 3', count: 1 }
// ]

// Word-by-word diff
const wordDiff = diffWords("Hello world", "Hello there");

// Character-by-character diff
const charDiff = diffChars("abc", "adc");

Patience Algorithm (Better for Code)

import { diffLinesPatiently } from "@lpm.dev/neo.diff";

// Produces more readable diffs for code with moved blocks
const diff = diffLinesPatiently(oldCode, newCode);

Structured Diffing

import { diffJson, diffArrays, diffObjects } from "@lpm.dev/neo.diff";

// JSON diff (stringifies and diffs line-by-line)
const jsonDiff = diffJson({ name: "John", age: 30 }, { name: "John", age: 31 });

// Array diff
const arrayDiff = diffArrays([1, 2, 3], [1, 2, 4]);

// Object diff (microdiff-style with CREATE/REMOVE/CHANGE)
const objectDiff = diffObjects({ a: 1, b: 2 }, { a: 1, b: 20, c: 3 });
// => [
//   { type: 'CHANGE', path: ['b'], oldValue: 2, value: 20 },
//   { type: 'CREATE', path: ['c'], value: 3 }
// ]

Patch Generation & Application

import { createPatch, formatPatch, applyPatch } from "@lpm.dev/neo.diff";

const old = "line 1\nline 2\nline 3";
const newText = "line 1\nmodified\nline 3";

// Create patch
const patch = createPatch(old, newText, "file.txt", "file.txt");

// Format as unified diff
const formatted = formatPatch(patch);
// --- file.txt
// +++ file.txt
// @@ -1,3 +1,3 @@
//  line 1
// -line 2
// +modified
//  line 3

// Apply patch
const result = applyPatch(old, formatted);
// => 'line 1\nmodified\nline 3'

API Documentation

Text Diffing

diffLines(oldText, newText, options?)

Compare text line by line. Most commonly used diff function.

diffLines("line 1\nline 2", "line 1\nline 3");

Options:

  • ignoreWhitespace: Ignore whitespace differences
  • ignoreCase: Ignore case differences
  • context: Number of context lines for patches (default: 3)

diffLinesPatiently(oldText, newText, options?)

Line diff using Patience algorithm. Better for code with moved sections.

diffWords(oldText, newText, options?)

Compare text word by word.

diffChars(oldText, newText, options?)

Compare text character by character.

diffSentences(oldText, newText, options?)

Compare text sentence by sentence.

Structured Diffing

diffJson(oldValue, newValue, options?)

Diff JSON-serializable values. Stringifies with pretty-printing and diffs line-by-line.

diffJson({ name: "John" }, { name: "Jane" });

diffArrays(oldArray, newArray, equals?)

Diff arrays element by element. Supports custom equality function.

diffArrays([1, 2, 3], [1, 2, 4]);

// With custom equality
diffArrays(users, newUsers, (a, b) => a.id === b.id);

diffObjects(oldObj, newObj)

Diff objects with microdiff-style output (CREATE/REMOVE/CHANGE operations).

diffObjects({ a: 1 }, { a: 2, b: 3 });
// => [
//   { type: 'CHANGE', path: ['a'], oldValue: 1, value: 2 },
//   { type: 'CREATE', path: ['b'], value: 3 }
// ]

applyChanges(obj, changes)

Apply structured changes to recreate an object.

const changes = diffObjects(oldObj, newObj);
const result = applyChanges(oldObj, changes);
// result === newObj

Patch Support

createPatch(oldStr, newStr, oldFileName?, newFileName?, options?)

Create a patch structure from two strings.

formatPatch(patch, oldHeader?, newHeader?)

Format a patch as unified diff string (compatible with git diff and patch command).

createTwoFilesPatch(oldStr, newStr, oldPath, newPath, oldHeader?, newHeader?, options?)

Create a formatted unified diff patch in one step.

applyPatch(source, patch, options?)

Apply a unified diff patch to source text. Returns patched string or null if failed.

applyPatch(source, patchString, { fuzzFactor: 2 });

parsePatch(patchContent)

Parse a unified diff patch string into structured format.

validatePatch(patch)

Validate a parsed patch structure.

Algorithms

myersDiff(oldSeq, newSeq, equals?)

Myers diff algorithm for any sequence. Fast general-purpose algorithm.

patienceDiff(oldSeq, newSeq, equals?)

Patience diff algorithm. Better for code with moved sections.

Utilities

computeLCS(oldSeq, newSeq, equals?)

Compute Longest Common Subsequence.

editDistance(str1, str2)

Calculate Levenshtein edit distance.

similarity(str1, str2)

Calculate similarity score (0-1) between strings.

normalizeText(text, ignoreWhitespace?, ignoreCase?)

Normalize text for comparison.

Performance

Operation Ops/sec Mean Time
Small text (3 lines) 1,164,097 0.9 ฮผs
Medium text (100 lines) 55,065 18.2 ฮผs
Large text (1000 lines) 754 1.33 ms
Small object (3 keys) 811,201 1.2 ฮผs
Config diff 850,871 1.2 ฮผs
API response diff 134,551 7.4 ฮผs

Algorithm Comparison

  • Myers: 23x faster for large files, best general-purpose choice
  • Patience: 1.1x faster for code with moved blocks, more readable output

Bundle Size

Metric Size Comparison
Minified 12 KB 86% smaller than diff (88 KB)
Gzipped 3.99 KB Excellent for CDN
Tree-shaken 50-200 bytes/function Import only what you need

Why Choose neo.diff?

vs diff package (88 KB, 20M downloads/week)

  • โœ… 86% smaller (12 KB vs 88 KB)
  • โœ… TypeScript-first (diff has community types only)
  • โœ… Tree-shakeable (diff is all-or-nothing)
  • โœ… Modern ESM (diff is CommonJS-first)
  • โœ… Comparable performance (sometimes faster)

vs fast-diff (5 KB, 3M downloads/week)

  • โœ… More features (patches, structured diff, multiple algorithms)
  • โœ… TypeScript native (fast-diff has no types)
  • โœ… Better API (more intuitive, better documented)
  • โš ๏ธ Slightly larger (12 KB vs 5 KB, but way more functionality)

vs microdiff (1 KB, 5M downloads/week)

  • โœ… Text diffing (microdiff only does objects)
  • โœ… Patch support (microdiff has none)
  • โœ… Multiple algorithms (microdiff has one approach)
  • โš ๏ธ Larger (12 KB vs 1 KB, but microdiff is object-only)

Tree-Shaking

Import only what you need:

// Full bundle (~12 KB)
import * as diff from "@lpm.dev/neo.diff";

// Single function (~50-200 bytes)
import { diffLines } from "@lpm.dev/neo.diff";

// Category import
import { diffLines, diffWords } from "@lpm.dev/neo.diff/text";
import { diffJson, diffObjects } from "@lpm.dev/neo.diff/structured";

Options

DiffOptions

interface DiffOptions {
  context?: number; // Number of context lines (default: 3)
  ignoreWhitespace?: boolean; // Ignore whitespace (default: false)
  ignoreCase?: boolean; // Ignore case (default: false)
  algorithm?: "myers" | "patience" | "histogram" | "lcs";
}

PatchOptions

interface PatchOptions {
  context?: number; // Context lines (default: 3)
  newlineAtEnd?: boolean; // Add newline at end (default: true)
  ignoreWhitespace?: boolean; // Ignore whitespace (default: false)
}

Real-World Examples

Git-style Diff

import { createTwoFilesPatch } from "@lpm.dev/neo.diff";

const patch = createTwoFilesPatch(
  oldContent,
  newContent,
  "a/src/file.ts",
  "b/src/file.ts",
  "Original",
  "Modified",
);

console.log(patch);
// --- a/src/file.ts  Original
// +++ b/src/file.ts  Modified
// @@ -1,5 +1,5 @@
// ...

Testing Framework

import { diffLines } from "@lpm.dev/neo.diff";

function assertTextEquals(expected: string, actual: string) {
  if (expected === actual) return;

  const diff = diffLines(expected, actual);
  const message = diff
    .map((change) => {
      if (change.added) return `+ ${change.value}`;
      if (change.removed) return `- ${change.value}`;
      return `  ${change.value}`;
    })
    .join("\n");

  throw new Error(`Text mismatch:\n${message}`);
}

API Response Validation

import { diffObjects } from "@lpm.dev/neo.diff";

const expected = { status: 200, data: { id: 1, name: "Test" } };
const actual = { status: 200, data: { id: 1, name: "Production" } };

const changes = diffObjects(expected, actual);
// => [{ type: 'CHANGE', path: ['data', 'name'], oldValue: 'Test', value: 'Production' }]

Configuration Diff

import { diffJson } from "@lpm.dev/neo.diff";

const oldConfig = { port: 3000, host: "localhost" };
const newConfig = { port: 8080, host: "localhost", ssl: true };

const diff = diffJson(oldConfig, newConfig);
// See exactly what changed in config

TypeScript

Full TypeScript support with strict mode:

import type {
  DiffResult,
  DiffChange,
  StructuredDiffChange,
} from "@lpm.dev/neo.diff";

const changes: DiffResult = diffLines(old, newText);
const change: DiffChange = changes[0];

if (change.added) {
  console.log("Added:", change.value);
}

Requirements

  • Node.js 18+ or modern browsers
  • TypeScript 5.0+ (for TypeScript users)

License

MIT

diffmyerspatiencepatchunified-diffline-diffword-diffchar-diffjson-diffarray-diffobject-diffgit-diffdiff-match-patchfast-difftree-shakeabletypescriptzero-dependency
Unlimited AccessInstall as many Pool packages as you need.
Fund Real WorkEvery install you run sends revenue directly to the developer who built it.

Taxes calculated at checkout based on your location.

Weekly Installs
3
Version
1.0.0
Published
LicenseMIT
Size703.10 KB
Files47
Node version>= 18
TypeScriptYes