diff、diff2html、diff2html-cli、git-diff は、テキストの差分(diff)を扱うための JavaScript エコシステムを構成する主要なパッケージです。diff は文字列やオブジェクトの差分を計算するコアエンジンとして機能し、diff2html はその差分データを人間が読みやすい HTML 形式に変換して可視化します。diff2html-cli は diff2html の機能を利用したコマンドラインツールであり、git-diff は Git の差分出力に特化したパッケージですが、維持状況や機能面で他の選択肢と比較する必要があります。これらはコードレビューツール、バージョン管理 UI、変更履歴の可視化など、フロントエンドおよびバックエンドのさまざまなシナリオで利用されます。
JavaScript エコシステムにおいて、コードやテキストの「差分(diff)」を扱うことは、コードレビューツール、バージョン管理システム、設定ファイルの比較など、多くの重要なシナリオで必要となります。diff、diff2html、diff2html-cli、git-diff は、この課題に取り組むための代表的なパッケージですが、それぞれが解決する問題と役割が明確に異なります。
本稿では、これらのパッケージの技術的な違い、適切な使い分け、そして実装レベルの詳細を比較します。
まず理解すべきは、これらが「差分の計算」と「差分の描画」という異なる工程を担当している点です。
diff は、2 つのテキストやオブジェクトの間の差異を「計算」するエンジンです。
// diff: 差分の計算
import * as Diff from 'diff';
const text1 = "Hello World";
const text2 = "Hello JavaScript";
const changes = Diff.diffWords(text1, text2);
// 結果:[{ value: 'Hello ' }, { value: 'World', removed: true }, { value: 'JavaScript', added: true }]
diff2html は、既存の差分データ(Unified Diff 形式など)を受け取り、HTML として「描画」します。
diff パッケージや git diff コマンドの出力を入力として想定しています。// diff2html: 差分の描画
import { Diff2Html } from 'diff2html';
const diffInput = `
diff --git a/file.js b/file.js
index 123..456 100644
--- a/file.js
+++ b/file.js
@@ -1 +1 @@
-console.log('old')
+console.log('new')
`;
const html = Diff2Html.html(diffInput, {
drawFileList: true,
matching: 'lines',
outputFormat: 'line-by-line'
});
// 結果:HTML 文字列(テーブル形式の差分ビュー)
diff2html-cli は、diff2html の機能をターミナルから利用できるようにしたツールです。
# diff2html-cli: コマンドラインでの利用
# Git の差分をパイプして HTML ファイルを生成
git diff | npx diff2html-cli -i stdin -o diff-report.html
git-diff は、Git の差分出力を扱うことに特化しているとされますが、npm 上の同名パッケージは維持状況に注意が必要です。
child_process で git diff を実行するラッパーか、差分文字列のパースを試みるものです。diff パッケージの Unified Diff パース機能と重複することが多く、依存を増やすだけのリスクがあります。// git-diff: 例(パッケージによる実装差異大)
// 注意:多くの git-diff パッケージは更新が停止している可能性があります
import gitDiff from 'git-diff';
// 実装はパッケージに依存しますが、基本的には git コマンドのラッパーです
// const result = await gitDiff({ cwd: './repo' });
差分を「どのように」計算するかは、ユースケースによって重要です。diff パッケージはこの点で最も柔軟です。
diff は、比較の粒度を明示的に選べます。
diffChars: 文字単位(スペルチェックなど)diffWords: 単語単位(文章の比較)diffLines: 行単位(コードの比較)diffJson: JSON オブジェクトの構造比較import * as Diff from 'diff';
// 行単位での比較(コードレビュー向け)
const lineDiff = Diff.diffLines(
"line1\nline2\nline3",
"line1\nline2_new\nline3"
);
lineDiff.forEach(part => {
const color = part.added ? 'green' : part.removed ? 'red' : 'grey';
// process colored part
});
// JSON 構造の比較
const jsonDiff = Diff.diffJson(
{ a: 1, b: 2 },
{ a: 1, b: 3 }
);
diff2html は計算を行いません。
// diff2html: 計算は行わず、入力された diff 文字列をレンダリングするのみ
// 誤った形式の diff 文字列を入れると正しく表示されません
const html = Diff2Html.html(validUnifiedDiffString);
git-diff は、Git のアルゴリズムに依存します。
// git-diff: 環境依存性が高い
// ブラウザでは通常動作せず、Node.js 環境かつ Git が必要
// const diff = require('git-diff');
// diff(oldPath, newPath); // 内部で git diff コマンドを実行
差分をユーザーに見せる際、どのように表示するかは UX に直結します。
diff2html は豊富な設定オプションを提供します。
outputFormat: 行ごと(line-by-line)か、単語ごと(word-by-word)か。matching: 行のマッチングアルゴリズム(lines, words, none)。drawFileList: ファイル一覧を表示するかどうか。// diff2html: 詳細な設定が可能
const html = Diff2Html.html(diffString, {
drawFileList: true,
matching: 'words',
outputFormat: 'word-by-word',
renderNothingWhenEmpty: false
});
// 生成された HTML には特定のクラスが付与されるため、CSS で装飾可能
// .d2h-file-wrapper, .d2h-file-header など
diff は可視化機能を持ちません。
diff2html などに渡す必要があります。// diff: 独自のレンダリングロジックを実装する起点
const changes = Diff.diffLines(oldText, newText);
const html = changes.map(part => {
const className = part.added ? 'added' : part.removed ? 'removed' : 'normal';
return `<span class="${className}">${part.value}</span>`;
}).join('');
diff2html-cli は設定をコマンドライン引数で行います。
# diff2html-cli: 引数で設定を渡す
npx diff2html-cli -i stdin -o report.html --matching words --output-format word-by-word
git-diff は可視化機能を含みません。
パッケージ選定において、維持状況(Maintenance Status)は重要な要素です。
diff と diff2html は、それぞれ分野において事実上の標準(De-facto Standard)として長く維持されています。
diff は kpdecker によって管理され、多くの主要ライブラリで依存されています。diff2html は rtfpessoa によって管理され、GitHub のプルリクエスト表示などでも類似の技術が使われています。diff2html-cli は diff2html に追随して更新されます。
git-diff は注意が必要です。
diff パッケージの parseUnifiedDiff などで代用可能なことが多く、あえてリスクを取るメリットが薄いです。diff + diff2html の組み合わせ、または Git コマンドの直接実行を推奨します。// 推奨されないパターン(git-diff パッケージへの依存)
// import gitDiff from 'git-diff'; // 維持されていない可能性あり
// 推奨パターン(diff パッケージの利用)
import * as Diff from 'diff';
const parsed = Diff.parsePatch(gitDiffOutputString);
実際の開発では、これらを単独で使うよりも、組み合わせて使うことがほとんどです。
ユーザーが 2 つのテキストを入力し、差分を表示したい場合。
diff パッケージで差分を計算。diff が提供する機能、または自作)。diff2html で HTML 生成。import * as Diff from 'diff';
import { Diff2Html } from 'diff2html';
function compareAndRender(oldText, newText) {
// 1. 差分計算
const changes = Diff.diffLines(oldText, newText);
// 2. Unified Diff 形式へ変換(簡易例)
// 実際は Diff.createTwoFilesPatch などの使用を推奨
const diffString = Diff.createTwoFilesPatch(
'old.txt', 'new.txt', oldText, newText
);
// 3. HTML 描画
return Diff2Html.html(diffString, {
outputFormat: 'line-by-line'
});
}
プルリクエスト作成時に、変更内容を HTML レポートとしてアーティファクトに残したい場合。
git diff コマンドで差分テキストを取得。diff2html-cli で HTML ファイルを生成。# CI スクリプト例
git diff origin/main...HEAD > changes.diff
npx diff2html-cli -i changes.diff -o report.html
# report.html をアーティファクトとしてアップロード
diff2html のデザインが合わない場合。
diff パッケージで差分配列を取得。// React 例
import * as Diff from 'diff';
function DiffView({ old, new: newText }) {
const diffs = Diff.diffLines(old, newText);
return (
<div className="diff-container">
{diffs.map((part, index) => {
const color = part.added ? '#e6ffec' : part.removed ? '#ffebe9' : 'white';
return (
<div key={index} style={{ backgroundColor: color }}>
{part.value}
</div>
);
})}
</div>
);
}
| 特徴 | diff | diff2html | diff2html-cli | git-diff |
|---|---|---|---|---|
| 主な役割 | 差分の計算 | 差分の HTML 描画 | CLI での描画 | Git 差分ラッパー |
| 入力 | 文字列、オブジェクト | Unified Diff 文字列 | Diff 文字列、ファイル | Git リポジトリ、パス |
| 出力 | 差分配列、Patch 文字列 | HTML 文字列、JSON | HTML ファイル | 差分文字列 |
| UI 機能 | なし | 豊富(シンタックス HL 等) | 豊富 | なし |
| 維持状況 | ✅ 活発 | ✅ 活発 | ✅ 活発 | ⚠️ 不明確(要確認) |
| 推奨度 | ⭐⭐⭐⭐⭐ (計算) | ⭐⭐⭐⭐⭐ (描画) | ⭐⭐⭐⭐ (CI 用) | ⭐ (非推奨) |
差分処理を要件とする場合、単一の「万能パッケージ」を探すのではなく、工程ごとに最適なツールを選ぶのが賢明です。
diff を使う
diff2html を使う
diff2html-cli を使う
git-diff パッケージは避ける
diff パッケージまたは Git コマンドの直接実行を選択してください。この構成が、長期的なメンテナンス性と機能性のバランスにおいて最も優れています。
Git の差分出力に特化したパッケージですが、維持状況が不明確な場合が多く、新しいプロジェクトでの使用は推奨されません。代わりに、diff パッケージの unified diff パース機能を使用するか、child_process で Git コマンドを実行し、その出力を diff2html でレンダリングする構成が堅牢です。
アプリケーション内でプログラム的に差分を計算する必要がある場合は diff を選択してください。これは最も標準的で維持されているライブラリであり、行単位、文字単位、単語単位など、細かな粒度での比較が可能です。Git の差分出力をパースする際にも、このライブラリの統一差分(unified diff)パーサーが信頼できます。
計算された差分データを Web ブラウザ上で美しく表示する必要がある場合は diff2html を選択してください。Git の diff 出力や diff パッケージの出力を受け取り、シンタックスハイライト付きの HTML を生成します。React や Vue などのコンポーネント内で diff を可視化する場合の事実上の標準です。
CI/CD パイプラインやローカルのスクリプトから、コマンドラインで直接 HTML 形式の差分レポートを生成したい場合に選択してください。diff2html ライブラリをラップしており、Node.js のコードを書かずに素早くレポートを作成できます。
Returns the git diff of two strings
git-diff will use git (if installed) and printf (if available) to get the real git diff of two strings, viz the actual diff output produced by git itself.
As a fallback, if either command is unavailable, git-diff will instead use the diff module to produce a very good fake git diff.
If desired, you may then console.log the returned git diff. An example of actual output:

npm install --save git-diff
git-diff takes 3 arguments, the old string to diff, the new string to diff and optionally an options object
git-diff returns the git difference or undefined where there is no difference.
String diff example usage:
var gitDiff = require('git-diff')
var oldStr = 'fred\nis\nfunny\n'
var newStr = 'paul\nis\nfunny\n'
var diff = gitDiff(oldStr, newStr)
var assert = require('assert')
assert.equal(diff, '@@ -1,3 +1,3 @@\n-fred\n+paul\n is\n funny\n')
File diff example usage:
var gitDiff = require('git-diff')
var readFileGo = require('readfile-go') // or your preferred file reader
var oldStr = readFileGo(__dirname + '/oldStr.txt')
var newStr = readFileGo(__dirname + '/newStr.txt')
var diff = gitDiff(oldStr, newStr)
Available options are:
color | flags | forceFake | noHeaders | save | wordDiff
Default options are:
var options = {
color: false, // Add color to the git diff returned?
flags: null, // A space separated string of git diff flags from https://git-scm.com/docs/git-diff#_options
forceFake: false, // Do not try and get a real git diff, just get me a fake? Faster but may not be 100% accurate
noHeaders: false, // Remove the ugly @@ -1,3 +1,3 @@ header?
save: false, // Remember the options for next time?
wordDiff: false // Get a word diff instead of a line diff?
}
Further assistance is given below for options that are not self explanatory.

The flags option allows you to use any git diff flags
This only applies to real git diffs and will not effect the output if it is fake.
An example to illustrate:
var gitDiff = require('git-diff')
var oldStr = 'fred\n is \nfunny\n'
var newStr = 'paul\nis\n funny \n'
var diff = gitDiff(oldStr, newStr, {flags: '--diff-algorithm=minimal --ignore-all-space'})
var assert = require('assert')
assert.equal(diff, '@@ -1,3 +1,3 @@\n-fred\n+paul\n is\n funny \n')
Here, the use of --ignore-all-space prevents a difference being reported on the 2nd and 3rd lines.

git-diff will initially attempt to use git and printf to get the real git diff.
If it cannot, it instead returns a very good fake git diff.
A fake git diff is faster to produce but may not be 100% representative of a real git diff.
The flags option is ignored when faking and fake diffs never have a header.
However, if a fake is good enough and speed is of the essence then you may want to force a fake git diff.
The forceFake option allows you to do exactly that:
var gitDiff = require('git-diff')
var oldStr = 'fred\nis\nfunny\n'
var newStr = 'paul\nis\nfunny\n'
var diff = gitDiff(oldStr, newStr, {forceFake: true})
var assert = require('assert')
assert.equal(diff, '-fred\n+paul\n is\n funny\n')

Its annoying to keep passing the same options every time.
git-diff, if instructed to do so, will remember previously used options for you.
When the {save: true} option is used in a call to git-diff subsequent calls remember the options.
var gitDiff = require('git-diff')
var oldStr = 'fred\nis\nfunny\n'
var newStr = 'paul\nis\nfunny\n'
var diff1 = gitDiff(oldStr, newStr, {save: true, wordDiff: true})
var diff2 = gitDiff(oldStr, newStr)
var assert = require('assert')
assert.equal(diff1, '@@ -1,3 +1,3 @@\n[-fred-]{+paul+}\nis\nfunny\n')
assert.equal(diff2, '@@ -1,3 +1,3 @@\n[-fred-]{+paul+}\nis\nfunny\n')
Here, the second call remembers that the wordDiff option is on. {wordDiff: true} is now the default.
git-diff offers a promise based async solution:
var gitDiff = require('git-diff/async')
var oldStr = 'fred\nis\nfunny\n'
var newStr = 'paul\nis\nfunny\n'
gitDiff(oldStr, newStr).then(function(diff) {
var assert = require('assert')
assert.equal(diff, '@@ -1,3 +1,3 @@\n-fred\n+paul\n is\n funny\n')
})
How good is the fake git diff?
The diff module used for the fake diff does not use the same difference algorithm as git. As such, a line diff is likely to be identical to a git line diff whereas a word diff will have some variance.
How can I tell whether the returned git diff is real or fake?
If the@@ -1,3 +1,3 @@header is present then the returned git diff is real.
If the header is absent then either the noHeaders option is on or the returned git diff is fake.
Will my environment produce a real or fake git diff?
Linux and mac have the covetedprintfcommand available. On Windows git bash makesprintfaccessible.
Assuming that git is installed, any of these environments will produce a real git diff.
What's the difference between how God treats the righteous and the wicked?
And God saw that the light was good. And God separated the light from the darkness. Genesis 1:4 ESV
And He will do it again:
Let both grow together until the harvest, and at harvest time I will tell the reapers, “Gather the weeds first and bind them in bundles to be burned, but gather the wheat into my barn.” Matthew 13:30 ESV
Much love :D