diff
PoolModern, tree-shakeable diff library - 70% smaller than diff, multiple algorithms, TypeScript-native
@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.diffQuick 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 differencesignoreCase: Ignore case differencescontext: 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 === newObjPatch 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 configTypeScript
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
Taxes calculated at checkout based on your location.