# jsdiff: token-level text diffing as a JavaScript library

> The npm package behind a generation of diff viewers, built on Myers' 1986 algorithm and split into character, word, line and sentence granularity depending on what you want highlighted.

**kpdecker/jsdiff** — A javascript text differencing implementation.

- Repository: https://github.com/kpdecker/jsdiff
- Stars: 9,212 · Forks: 545
- Language: JavaScript
- License: BSD-3-Clause
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/kpdecker-jsdiff

## One algorithm, several choices about what a token is

The README describes the whole library as three steps, and the third step is the boring one. The interesting part is the second: split both texts into arrays of tokens, then find the smallest set of single-token insertions and deletions that turns the first array into the second. The library implements the algorithm proposed in "An O(ND) Difference Algorithm and its Variations" by Myers from 1986, which is the same family of algorithm behind GNU diff and most of what you have seen in a terminal.

What jsdiff adds is not the algorithm but the choice of granularity. `diffChars` treats each Unicode code point as a token, `diffWords` treats words and punctuation marks as tokens while ignoring whitespace when computing, `diffWordsWithSpace` also keeps whitespace runs and newlines as tokens, `diffLines` works line by line, and `diffSentences` splits on `.`, `!` and `?` followed by whitespace. The output shape is the same in every case: an ordered array of change objects, each one an insertion, a deletion, or a kept run.

That structure is what makes the library composable. A diff viewer does not have to choose between character and word highlighting if it can call the function it wants and read the same change object shape either way. Installation is a single package:

```bash
npm install diff --save
```

## Imports, module systems, and the browser global

The packaging section covers three distinct consumption paths, and it matters which one you take. In an environment with module support, both the ES module and CommonJS forms expose the whole API by name:

```javascript
import {diffChars, createPatch} from 'diff';
```

```javascript
const {diffChars, createPatch} = require('diff');
```

For a web page served without a module system, the README points at `dist/diff.js` or `dist/diff.min.js`, which create a global called `Diff` holding the entire API as properties. The repository tree confirms this is a real distribution path rather than an aspiration: there is a `rollup.config.mjs` for the bundle, a `.babelrc`, an `eslint.config.mjs`, and a `tsconfig.json`.

That combination is worth noting against the repository language metadata, which reports JavaScript. A `tsconfig.json` alongside a `test-d/` directory suggests the project maintains type declarations and type-level tests even though the implementation language is JavaScript, which is a common shape for a library that wants to offer types without forcing TypeScript on its users. There is also a `karma.conf.js` and a `test/` directory, so the test suite runs in a browser context as well as in Node.

## Token equality is the option that decides the output

Almost every surprising result from this library traces back to the equality rule in step two, and the README is unusually explicit about it. Usually two tokens are equal when `===` says so, but individual functions accept an alternative. The worked example the docs give is case sensitivity: `diffChars("Foo", "FOOD")` requires two deletions and three insertions, while passing `{ignoreCase: true}` reduces that to a single insertion of the `D`, because `o` and `O` now match.

The line-level function shows how much surface area this opens. `ignoreWhitespace` makes leading and trailing whitespace irrelevant when deciding whether two lines match. `ignoreNewlineAtEof` treats a trailing newline as equivalent to its absence, which is what you want when diffing a file that was saved with and without one. `stripTrailingCr` removes carriage returns before diffing, which the README describes as the way to get a useful diff between UNIX and Windows text files.

Then there is `newlineIsToken`, and the README attaches a warning to it that is worth internalising rather than skipping. With the option off, changing a blank line into a line containing only spaces reads as no change at all when `ignoreWhitespace` is on. Turn `newlineIsToken` on and the same edit becomes an insertion, because the content of a completely blank line is not a token in that mode. The default, with the option off, is the better fit for patches and machine-readable output; the option on is described as the more human friendly form of `diffLines`.

## Word diffing and the Intl.Segmenter decision

`diffWords` is where the library gets genuinely opinionated, and the opinion is defensible. By default it does not use `Intl.Segmenter`, splitting on regexes instead. The README states plainly why: the results stay consistent across environments, because `Intl.Segmenter` behaviour is only loosely specified and a browser implementation could change dramatically in future.

The cost of that consistency is quality on non-English text, and the docs are specific about the loss. Without a segmenter, word boundaries are guessed from punctuation and whitespace. With one, the segmenter's built-in dictionaries can identify which sequences of adjacent Chinese characters form words, and a segmenter instantiated for a language can apply language-specific rules, such as treating a Swedish colon separated contraction as a single word.

There is a middle path the README offers for people who want quality without environment drift: pass an `Intl.Segmenter` polyfill instead of relying on the engine's native implementation. The segmenter must be constructed with `granularity` of `'word'` to be accepted, and the option is named `intlSegmenter` on the diff functions that support it. `diffSentences` points at the same mechanism for anyone who needs more reliable sentence breaks than `.`, `!` and `?` provide.

## Reading the repository signals

The metadata shows a JavaScript project with 9,212 stars, 545 forks and 22 open issues, last pushed on 2026-09-19, on the `master` branch and not archived. An open issue count in the twenties against that star total is unusually low for a package at this level of adoption, which usually means either very responsive maintenance or a lot of usage through a wrapper rather than direct issue filing. Both are consistent with a library that other packages depend on rather than one people file bugs against directly.

The default branch being `master` rather than `main` is a small sign of age. So is a `release-notes.md` file sitting in the tree alongside `CHANGELOG`-style history rather than relying solely on GitHub releases. The repository also keeps two runnable examples, `examples/node_example.js` and `examples/web_example.html`, which is a fast way to see the change object shape in both environments without reading the test suite.

What the repository does not carry is a homepage field, and there is no indication in the tree of what the intended modern entry points are beyond the README and the example files. For a package this widely used, that means the practical documentation surface is the README plus the linked online demo rather than a maintained docs site.

## Where jsdiff fits against the alternatives

The honest comparison is not with other JavaScript diff libraries so much as with not needing a library at all. The browser and Node both ship diffing behaviour in the form of `Intl.Segmenter` for tokenization, but neither ships a Myers implementation, so if you need change objects for rendering, something like jsdiff is still the shortest path.

The comparison worth making is against shelling out to the `diff` binary or a native module. jsdiff is pure JavaScript with a small dependency footprint, it runs in a browser without a build step beyond taking the bundled file, and it gives you the intermediate tokenization as a first class decision. What you give up is raw speed on very large inputs, where a native implementation with a tighter inner loop will win, and the ability to lean on GNU diff's heuristics for line matching that have been tuned for decades.

For a diff view, a merge preview, or an inline redline, the granularity-plus-equality model is the feature. Choose the function whose token definition matches what you are showing a reader, and set the equality option that matches the kind of edit you expect to see as unimportant, and the library does the rest.

## Conclusion

jsdiff earns its adoption by making granularity an explicit choice instead of a hidden default. If you are highlighting a single changed character in an editor, `diffChars` is the function; if you are rendering a readable review of prose, `diffWords` with an `Intl.Segmenter` gets you word-level boundaries in languages that regexes cannot segment. Start with the token model rather than the API list, because every option in this library is a statement about what counts as the same token, and that one decision explains the rest of the surface area.

## FAQ

### Which jsdiff function should be used for word-level highlighting?

`diffWords` is the one, treating each word and punctuation mark as a token while ignoring whitespace when computing the diff. For better results on non-English text, pass an `Intl.Segmenter` with `granularity` of `'word'` through the `intlSegmenter` option. Use `diffWordsWithSpace` instead when whitespace runs and newlines should also be highlighted as changes.

### Why does jsdiff ignore whitespace by default, and how do I turn it off?

`diffWords` is defined to ignore whitespace when computing the diff, while preserving it as far as possible in the resulting change objects. If whitespace should be treated as meaningful, switch to `diffWordsWithSpace`, which treats each word, punctuation mark, newline, or run of non-newline whitespace as a token. At line level the separate `ignoreWhitespace` option controls the opposite direction, defaulting to `false`.

### What algorithm does jsdiff use and why does that matter for large inputs?

The library implements the algorithm proposed in "An O(ND) Difference Algorithm and its Variations" by Myers from 1986, the same family of algorithm used by GNU diff. Its cost grows with the size of the input and the size of the difference, so inputs that are largely similar are cheap and inputs that are entirely rewritten are the expensive case. Tokenizing a large file character by character with `diffChars` is much heavier than doing the same with `diffLines`.

### Can jsdiff be used in a browser without a bundler?

Yes. The README states that for a web page served without a module system you can use `dist/diff.js` or `dist/diff.min.js`, which create a global called `Diff` containing the entire API as properties. The repository also includes `examples/web_example.html`, which shows the pattern without a build step.

### How do I make jsdiff ignore a trailing newline or carriage return difference?

On `diffLines`, set `ignoreNewlineAtEof` to treat a missing final newline as equal to one present, which matters when comparing a file saved with and without a terminating newline. Set `stripTrailingCr` to remove carriage returns first, which is the option to reach for when diffing UNIX text against Windows text. Both default to `false`.

## Sources

- [Issues](https://github.com/kpdecker/jsdiff/issues)
- [kpdecker/jsdiff on GitHub](https://github.com/kpdecker/jsdiff)
- [License: BSD-3-Clause](https://github.com/kpdecker/jsdiff/blob/master/LICENSE)
- [README](https://github.com/kpdecker/jsdiff/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kpdecker-jsdiff
