mrkeyoor.com_
Sun 20 Sept 02:38 UTC
npmUtilsupdated 18 Sept 2026

diff review

diff 9.0.0, also known as jsdiff, finds insertions and deletions between strings or token arrays with a Myers edit algorithm. Callers choose whether a token means a Unicode code point, word, line, sentence, CSS token, JSON line, or array item, then receive ordered change objects. The same package creates, parses, reverses, formats, and applies unified patches. Version 9 adds Git extended-header support for creations, deletions, renames, copies, modes, and binary markers, and it correctly quotes unusual filenames. It also drops ES5 targets.

129.4Mdownloads / wk
Verdict

diff 9.0.0 installed in 0.4 seconds with 0 direct dependencies and produced an 8.1 KB gzipped browser bundle in our sandbox, with 0 audit findings. Install it for text spans or unified-patch work, but put work limits around unrelated inputs and build your own safe filesystem layer for Git patches.

We installed it

Lab card: what happened when we installed diffScreenshot of diff documentation
Install✓ · 0.4s3 packages on disk · 1 MB
ImportESM import works · require() works · ESM package with exports map
Browser8.1 KBgzipped (22.5 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does diff install cleanly?

Yes. In a fresh container with an empty cache, npm install diff finished in 0.4s, leaving 3 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does diff add to a browser bundle?

8.1 KB gzipped (22.5 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does diff work with both ESM and CommonJS?

Yes. Both import 'diff' and require('diff') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does diff include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

diff or fast-diff: which should you use?

fast-diff: Use it for a compact character-level diff when patches and multiple tokenizers are unnecessary. diff 9.0.0 installed in 0.4 seconds with 0 direct dependencies and produced an 8.1 KB gzipped browser bundle in our sandbox, with 0 audit findings.

When should you not use diff?

Readers expect semantic cleanup that groups a noisy minimal edit into a human editorial change. jsdiff reports token edits and does not rewrite them for readability.

API stability3/5Core names such as `diffChars`, `diffWords`, `diffLines`, `createPatch`, `parsePatch`, and `applyPatch` remain recognizable, but majors 6 through 9 changed token rules, callbacks, types, patch validation, fuzzy application, and supported runtimes. Version 9 adds optional filenames and rejects unpaired file headers. Pin the major when generated patches or exact change spans become stored application data.
Docs5/5The README documents each tokenizer's definition, its options, change-object fields, abort behavior, TypeScript overloads, every patch operation, line-ending conversion, fuzzy matching, and Git-specific properties. Release notes spell out v9 compatibility breaks with concrete malformed and valid patch cases. A reader can tell where parsing ends and filesystem responsibility begins without consulting source code.
Maintenance4/5GitHub shows 9,195 stars, 21 combined open issues and pull requests, an unarchived repository, and a push on 24 August 2026. npm lists 9.0.0 as latest, while repository release notes already describe 9.1.0 prerelease fixes for `__proto__`, non-callable `toJSON`, and a line-ending corner case. Active fixes are visible, though there is no GitHub release object to centralize the published history.
Ecosystem5/5npm counted 144,382,566 downloads for 19 through 25 August 2026. Our Node 22 check loaded version 9 through ESM and CommonJS, and its bundled declarations cover TypeScript consumers. The package also targets current browsers and emits ordinary change arrays or unified patch text that many renderers can consume. Display escaping and file policy remain outside its scope.

Use it if

  • A review screen, editor, assertion failure, or history view needs added, removed, and unchanged text spans.
  • JavaScript must create or consume unified patches without invoking the system `diff` and `patch` commands.
  • Git patch metadata such as rename, copy, mode, create, and delete headers must survive parsing or formatting.
  • Untrusted comparisons need an explicit `timeout` or `maxEditLength` rather than unlimited edit-distance work.
Skip it if

Setup reality

We installed diff 9.0.0 in a fresh Node 22 Bookworm sandbox in 0.4 seconds. The environment ended with 3 packages and 1 MB on disk. npm audit returned 0 known vulnerabilities across all severity levels. The package declares 0 direct and 0 peer dependencies, carries bundled TypeScript declarations, and worked through both require() and ESM import. Our esbuild browser test measured 22.5 KB minified and 8.1 KB gzipped.

There are no credentials or configuration files. The choice that changes results is the tokenizer. diffWords ignores whitespace while matching, diffWordsWithSpace preserves whitespace tokens, and diffLines exposes options for CRLF, trailing whitespace, and a missing final newline. An Intl.Segmenter improves word boundaries for languages such as Chinese, but native segmenters may differ across engines. Persisted diffs need one pinned segmenter implementation if repeatability matters.

Expensive comparisons need a ceiling. A synchronous call with timeout or maxEditLength returns undefined after giving up. Callback mode also returns undefined immediately and delivers its eventual result to the callback while yielding between work slices. TypeScript models those calls with separate overloads, so keep the options object typed instead of routing it through any. Browser UI code should still move large jobs off the main thread.

applyPatch returns false when a hunk cannot be placed; an empty string is a successful result, so a truthiness check is wrong. fuzzFactor relaxes surrounding context but never excuses a missing deletion or mismatched lines immediately beside an insertion. Version 9 understands Git headers, yet applyPatches delegates file loading and saving to callbacks. Path containment, binary changes, copy order, modes, atomic writes, and cleanup stay in your application.

Patterns

Inspect character changes compare-characters

import { diffChars } from 'diff';

for (const change of diffChars('cafe', 'café')) {
  const kind = change.added ? 'added' : change.removed ? 'removed' : 'same';
  console.log(kind, change.value);
}

`diffChars` treats Unicode code points as tokens in version 9, and each change object carries explicit boolean flags.

Compare prose by word compare-words

import { diffWords } from 'diff';

const changes = diffWords(
  'Deploy the blue service.',
  'Deploy the green service.',
  { ignoreCase: true },
);

`diffWords` ignores whitespace for matching; use `diffWordsWithSpace` when whitespace itself must appear as a version 9 change.

Use language-aware word boundaries segment-localized-words

import { diffWords } from 'diff';

const segmenter = new Intl.Segmenter('zh', { granularity: 'word' });
const changes = diffWords('我喜欢蓝色', '我喜欢绿色', {
  intlSegmenter: segmenter,
});

Native `Intl.Segmenter` output can vary by engine; stored diffs need 1 fixed polyfill for reproducible token boundaries.

Normalize line-ending differences compare-lines

import { diffLines } from 'diff';

const changes = diffLines(windowsText, unixText, {
  stripTrailingCr: true,
  ignoreNewlineAtEof: true,
});

`stripTrailingCr` removes trailing carriage returns before comparison, while `ignoreNewlineAtEof` covers only the final newline.

Match array records by id compare-arrays

import { diffArrays } from 'diff';

const before = [{ id: 1 }, { id: 2 }];
const after = [{ id: 1 }, { id: 3 }];
const changes = diffArrays(before, after, {
  comparator: (left, right) => left.id === right.id,
});

The comparator receives an old-array item first and a new-array item second, which matters when equality is directional.

Stop a costly comparison limit-diff-work

import { diffLines } from 'diff';

const result = diffLines(oldText, newText, {
  timeout: 250,
  maxEditLength: 10_000,
});
if (result === undefined) {
  throw new Error('diff limit reached');
}

A bounded version 9 call returns `undefined` when either limit ends the search; it does not return a partial change list.

Run comparison in callback mode yield-during-diff

import { diffLines } from 'diff';

diffLines(oldText, newText, {
  timeout: 1_000,
  callback(result) {
    if (result === undefined) return console.error('timed out');
    render(result);
  },
});

The function itself returns `undefined` in callback mode, then yields between work slices until the callback receives a result.

Generate a reviewable patch create-unified-patch

import { createTwoFilesPatch, FILE_HEADERS_ONLY } from 'diff';

const patch = createTwoFilesPatch(
  'config.old', 'config.new', before, after, '', '',
  { context: 3, headerOptions: FILE_HEADERS_ONLY },
);

Three context lines are included here; some patch consumers reject optional `Index:` and underline headers, so `FILE_HEADERS_ONLY` removes them.

Read Git extended headers parse-git-patch

import { parsePatch } from 'diff';

for (const file of parsePatch(gitOutput)) {
  console.log({
    old: file.oldFileName,
    next: file.newFileName,
    rename: file.isRename,
    mode: file.newMode,
  });
}

Version 9 parses hunkless renames and mode changes, and filename fields may be `undefined` when no header identifies them.

Distinguish failure from empty output apply-single-patch

import { applyPatch } from 'diff';

const output = applyPatch(source, patch, { fuzzFactor: 0 });
if (output === false) {
  throw new Error('patch did not match');
}
await save(output);

Compare against `false` exactly because a patch that deletes all content succeeds with a 0-character string.

Invert a parsed patch reverse-patch

import { formatPatch, parsePatch, reversePatch } from 'diff';

const parsed = parsePatch(patchText);
const undoText = formatPatch(parsed.map((file) => reversePatch(file)));

Git binary patch bodies are opaque to version 9; reversing their metadata does not create a usable inverse binary delta.

Render changes without injecting HTML escape-rendered-diff

import { diffWords } from 'diff';

const fragment = document.createDocumentFragment();
for (const change of diffWords(before, after)) {
  const span = document.createElement('span');
  span.className = change.added ? 'add' : change.removed ? 'remove' : 'same';
  span.textContent = change.value;
  fragment.append(span);
}
output.replaceChildren(fragment);

Assigning `textContent` keeps the 1 compared string from becoming executable markup; jsdiff does not escape rendered output for you.

Alternatives

PackageRegistryPick it when
fast-diffnpmUse it for a compact character-level diff when patches and multiple tokenizers are unnecessary.
deep-diffnpmUse it when object paths and before-and-after values matter more than printable text.
jsondiffpatchnpmUse it for reversible object deltas, array move detection, and an optional HTML renderer.

More utils guides

lru-cache · type-fest · ajv · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.