A Highlight.js language plugin that adds rich
syntax highlighting for diff / patch files — supporting all three classic
diff formats: normal, context (-c), and unified (-u).
Inner language content (added/deleted code lines) is re-highlighted using the language detected from the file headers, so the token colours inside a diff match the surrounding codebase.
- All three diff formats: normal (
1c1,< / >), context (***/---), unified (@@) - File header file names highlighted as strings; timestamps as numbers
- Hunk and section markers highlighted as operators
- Deleted lines wrapped in
hljs-deletion, added lines inhljs-addition - Inner-language syntax highlighting — if the diff headers contain
.js,.ts,.py, etc. and the matching language is registered, the code inside the diff is highlighted in that language - Unrecognised lines treated as
hljs-comment(e.g.diff --gitpreamble) - Handles LF, CRLF, and CR line endings
diffOf(language)factory to supply a fallback inner language for diffs that carry no file-name headers
npm install highlightjs-diff
highlight.js≥ 11 is a peer dependency — install it separately if you haven't already:npm install highlight.js
const hljs = require("highlight.js");
const diff = require("highlightjs-diff");
hljs.registerLanguage("diff", diff.default ?? diff);
const code = `
--- a/src/index.ts\t2024-01-01 00:00:00
+++ b/src/index.ts\t2024-01-01 00:00:01
@@ -1,4 +1,4 @@
import foo from "./foo";
-const x = 0;
+const x = 1;
export { x };
`.trim();
const html = hljs.highlight(code, { language: "diff" }).value;
console.log(html);import hljs from "highlight.js";
import diff from "highlightjs-diff";
hljs.registerLanguage("diff", diff);
const result = hljs.highlight(code, { language: "diff" });Load Highlight.js first, then load this plugin:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/github-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js"></script>
<!-- highlightjs-diff -->
<script src="https://unpkg.com/highlightjs-diff/dist/index.js"></script>
<script>
hljs.registerLanguage("diff", window.hljsDiff?.default ?? window.hljsDiff);
hljs.highlightAll();
</script>Then mark up your code blocks:
<pre><code class="language-diff">
--- a/hello.js 2024-01-01 00:00:00
+++ b/hello.js 2024-01-01 00:00:01
@@ -1,3 +1,3 @@
function hello() {
- console.log("Hello, world!");
+ console.log("Hello, Highlight.js!");
}
</code></pre>The normal diff format (diff a b, without -c or -u) does not include
file-name headers, so the highlighter has no way to auto-detect the language
of the code being diffed. diffOf(language) returns a language definition
that uses the supplied language as the fallback for inner-line highlighting
whenever no file-name information is present in the diff:
import hljs from "highlight.js";
import { diffOf } from "highlightjs-diff";
// Normal diff lines will be inner-highlighted as TypeScript because there
// are no file headers from which the language can be detected.
hljs.registerLanguage("diff", diffOf("typescript"));For unified and context diffs that already include ---/+++ or ***/---
headers, the language is auto-detected from the file extension in those headers
and the diffOf argument is not used. The default export is equivalent to
diffOf("plaintext"), which leaves inner code unhighlighted when file headers
are absent.
| Element | Highlight class |
|---|---|
| Added lines | hljs-addition |
| Deleted lines | hljs-deletion |
Range / hunk operators (@@, ***, ---, +++, <, >) |
hljs-operator |
| File names in headers | hljs-string |
| Line numbers and timestamps | hljs-number |
Range command letters (a, c, d) |
hljs-keyword |
| Commas in ranges | hljs-punctuation |
| Unrecognised / preamble lines | hljs-comment |
The highlighter does not treat a diff as a flat sequence of prefixed lines.
Instead it reconstructs the two original source files from the diff and
re-highlights each one with a full recursive call to hljs.highlight(), so
that syntax tokens — string literals, keywords, comments — span correctly
across neighbouring lines, just as they would in a standalone source file.
The steps are:
-
Diff-type detection. The entire input is scored against normal, context, and unified line patterns before any tokens are emitted, so the correct parser is used unconditionally.
-
Incremental parsing. Lines are consumed one at a time. Header and hunk-marker lines are tokenised and emitted immediately (file names as
hljs-string, line numbers ashljs-number, operators ashljs-operator, etc.). Code content lines — deleted, added, and unchanged — are collected into a pending buffer instead of being emitted straight away. -
Source reconstruction. Whenever a hunk boundary is reached (a new hunk header, a new file header, or the end of input), the buffer is drained. Two virtual source files are reconstructed from the buffered lines: one for the old version (all non-added lines) and one for the new version (all non-deleted lines).
-
Recursive highlighting. Both reconstructed sources are passed independently to
hljs.highlight()using the language detected from the diff's file-name headers (or thediffOffallback). Giving each version its own complete, contiguous source text lets the inner highlighter apply multi-line tokenisation — nested template literals, block comments, and so on — correctly. -
Token re-incorporation. The HTML produced by the inner
hljs.highlight()calls is parsed back into a sequence ofstartScope/addText/endScopeactions. Those actions are then woven into the diff output so that every code line is wrapped in the appropriatehljs-deletionorhljs-additionscope while retaining the full inner-language token tree.
The default export (diffOf("plaintext")) highlights the diff structure
but leaves the inner code unstyled because normal diff has no file headers to
detect the language from:
1c1
< console.log("Hello, world!");
---
> console.log("Hello, Highlight.js!");Using diffOf("javascript") enables inner JavaScript highlighting:
1c1
< console.log("Hello, world!");
---
> console.log("Hello, Highlight.js!");File extensions in the ***/--- headers are used to auto-detect the inner
language:
*** a/hello.js 2024-01-01 00:00:00
--- b/hello.js 2024-01-01 00:00:01
***************
*** 1,4 ****
function hello() {
! console.log("Hello, world!");
}
--- 1,4 ----
function hello() {
! console.log("Hello, Highlight.js!");
}--- a/hello.ts 2024-01-01 00:00:00
+++ b/hello.ts 2024-01-01 00:00:01
@@ -1,4 +1,4 @@
function hello() {
- console.log("Hello, world!");
+ console.log("Hello, Highlight.js!");
}Note: The HTML samples above use inline styles matching the GitHub Dark theme palette. On platforms that strip inline styles (e.g. GitHub's Markdown renderer), the blocks will fall back to unstyled monospace text. Apply any Highlight.js theme stylesheet to restore the colours.
| highlight.js | highlightjs-diff |
|---|---|
| ≥ 11.9.0 | ✓ |
MIT © ABDK Consulting