Create custom diff implementations with configurable tokenization, comparison logic, and result processing. The base Diff class provides the core Myers algorithm implementation with customizable behavior through overridable methods.
Base constructor for creating custom diff implementations with the Myers algorithm.
/**
* Create a new Diff instance for custom diffing behavior
* @constructor
*/
function Diff();
interface DiffInstance {
/** Main diff method implementing Myers algorithm */
diff(oldString: string, newString: string, options?: DiffOptions): ChangeObject[];
/** Transform input before processing (override for custom behavior) */
castInput(value: any, options?: DiffOptions): string;
/** Convert string to array of tokens (override for custom tokenization) */
tokenize(value: string, options?: DiffOptions): string[];
/** Remove empty tokens from array (override for custom filtering) */
removeEmpty(array: string[]): string[];
/** Compare two tokens for equality (override for custom comparison) */
equals(left: string, right: string, options?: DiffOptions): boolean;
/** Join tokens back into string (override for custom joining) */
join(tokens: string[]): string;
/** Post-process change objects (override for custom result processing) */
postProcess(changeObjects: ChangeObject[], options?: DiffOptions): ChangeObject[];
}Usage Examples:
import { Diff } from "diff";
// Create custom diff for semantic code comparison
const semanticDiff = new Diff();
// Override tokenization to treat function names as single tokens
semanticDiff.tokenize = function(value) {
// Split on whitespace but keep function names together
return value.split(/(\s+|\b(?:function|class|const|let|var)\s+\w+\b)/)
.filter(token => token.length > 0);
};
// Override equality to ignore whitespace differences
semanticDiff.equals = function(left, right, options) {
return left.trim() === right.trim();
};
// Use the custom diff
const result = semanticDiff.diff(oldCode, newCode);The base Diff class provides default implementations for all overridable methods:
/**
* Default input transformation (identity function)
* @param value - Input value to transform
* @param options - Diff options
* @returns Transformed value
*/
castInput(value, options);
/**
* Default tokenization (character-by-character split)
* @param value - String to tokenize
* @param options - Diff options
* @returns Array of character tokens
*/
tokenize(value, options);
/**
* Default empty token removal (filters falsy values)
* @param array - Array of tokens
* @returns Array with empty tokens removed
*/
removeEmpty(array);
/**
* Default equality comparison (strict equality with optional case insensitivity)
* @param left - Left token to compare
* @param right - Right token to compare
* @param options - Diff options (supports ignoreCase and comparator)
* @returns True if tokens are considered equal
*/
equals(left, right, options);
/**
* Default token joining (simple string concatenation)
* @param tokens - Array of tokens to join
* @returns Joined string
*/
join(tokens);
/**
* Default post-processing (identity function)
* @param changeObjects - Array of change objects
* @param options - Diff options
* @returns Processed change objects
*/
postProcess(changeObjects, options);Advanced Custom Diff Example:
import { Diff } from "diff";
// Create a diff for comparing structured data as JSON
class JsonDiff extends Diff {
castInput(value, options) {
if (typeof value === 'object') {
return JSON.stringify(value, null, 2);
}
return value;
}
tokenize(value, options) {
// Split JSON into logical tokens: braces, keys, values, commas
return value.split(/(\{|\}|\[|\]|"[^"]*":\s*|"[^"]*"|,|\s+)/)
.filter(token => token.trim().length > 0);
}
equals(left, right, options) {
// Normalize whitespace for comparison
const normalize = str => str.replace(/\s+/g, ' ').trim();
return normalize(left) === normalize(right);
}
postProcess(changes, options) {
// Group consecutive changes of the same type
const grouped = [];
let current = null;
for (const change of changes) {
if (current &&
current.added === change.added &&
current.removed === change.removed) {
current.value += change.value;
current.count += change.count;
} else {
if (current) grouped.push(current);
current = { ...change };
}
}
if (current) grouped.push(current);
return grouped;
}
}
// Use the custom JSON diff
const jsonDiff = new JsonDiff();
const result = jsonDiff.diff(
{ name: "Alice", age: 30 },
{ name: "Alice", age: 31, city: "NYC" }
);Word-aware tokenization:
const wordDiff = new Diff();
wordDiff.tokenize = function(value) {
return value.split(/(\s+|\b)/).filter(token => token.length > 0);
};Case-insensitive comparison:
const caseInsensitiveDiff = new Diff();
caseInsensitiveDiff.equals = function(left, right, options) {
return left.toLowerCase() === right.toLowerCase();
};Custom result formatting:
const formattedDiff = new Diff();
formattedDiff.postProcess = function(changes, options) {
return changes.map(change => ({
...change,
type: change.added ? 'addition' : change.removed ? 'deletion' : 'unchanged'
}));
};All built-in diff functions (diffChars, diffWords, etc.) are implemented using customized Diff instances. You can examine their implementations for inspiration:
import { Diff, characterDiff, wordDiff, lineDiff } from "diff";
// These are pre-configured Diff instances
console.log(characterDiff instanceof Diff); // true
console.log(wordDiff instanceof Diff); // true
console.log(lineDiff instanceof Diff); // trueinterface DiffOptions {
callback?: (result: ChangeObject[]) => void;
maxEditLength?: number;
timeout?: number;
oneChangePerToken?: boolean;
ignoreCase?: boolean;
comparator?: (left: string, right: string) => boolean;
}
interface ChangeObject {
value: string;
added?: boolean;
removed?: boolean;
count: number;
}