cheat sheet

diff2html-cli

Package-level reference for the diff2html-cli renderer on npm — install, runtime support, peer-deps, and alternatives.

#npm#package#diff#html#cliupdated 05-31-2026

diff2html-cli

What it is

diff2html-cli is the command-line wrapper around the diff2html library — it consumes a unified diff (from git diff, svn diff, or stdin) and emits a styled HTML file with side-by-side or line-by-line views, optional dark theme, syntax highlighting, and a file-tree summary. It is most often used to attach a viewable diff to a code-review email or to render a static visual summary in CI.

Reach for diff2html-cli when reviewers want an HTML deliverable they can open in any browser. Reach for delta or riff instead if the goal is rich in-terminal diff viewing without leaving the shell.

Install

This is a global-CLI-style package, but local install + npx is the modern recommendation.

bash
npm install -g diff2html-cli

Output: added diff2html-cli to global packages — exposes diff2html bin

bash
npx diff2html-cli --help

Output: prints usage from the local cache

bash
pnpm add -g diff2html-cli

Output: added 1 package globally

bash
yarn global add diff2html-cli

Output: added diff2html-cli to global cache

bash
bun add -g diff2html-cli

Output: installed diff2html-cli globally; diff2html on PATH

bash
deno run --allow-all npm:diff2html-cli --help

Output: runs the CLI with explicit Deno permissions

Versioning & Node support

The current line is diff2html-cli@5.x. Versions are loosely paired with the underlying diff2html library (also 5.x at time of writing).

  • Node 16+ required (the CLI uses optional chaining and ESM-only deps internally).
  • CommonJS-friendly — the published bin loads via CJS and shells out to esbuild-bundled internals.
  • Semver — major bumps usually change the default template (line-by-line vs side-by-side) or the asset bundling strategy.

Package metadata

  • Maintainer: Rodrigo Fernandes (rtfpessoa on GitHub) and contributors
  • Project home: github.com/rtfpessoa/diff2html-cli
  • Library: github.com/rtfpessoa/diff2html — the renderer itself
  • Docs: diff2html.xyz
  • npm: npmjs.com/package/diff2html-cli
  • License: MIT
  • First released: 2015
  • Downloads: a few hundred thousand weekly downloads — niche but stable in CI and code-review workflows.

Peer dependencies & extras

diff2html-cli pulls in diff2html (the renderer), highlight.js (for syntax colouring), yargs (for argument parsing), and a small set of utilities. No peer-deps — everything ships as direct dependencies.

There is no extras flag. Customisation happens through CLI options:

  • --style=side / --style=line — layout
  • --highlightCode=true — syntax highlighting via highlight.js
  • --summary=closed|open|hidden — file-tree summary state
  • --format=html|json — rendering target
  • --output=preview|stdout|file — destination
  • --diffStyle=word|char — intra-line diff granularity

Alternatives

PackageTrade-off
delta (Rust)In-terminal pager replacement for git diff. Much faster, never produces HTML. Install via cargo or brew install git-delta.
riff (Rust)Similar to delta. Smaller feature set, faster startup.
diff-so-fancyOlder Perl-based formatter used by Atom and GitHub for years. Pipes through less.
GitHub / GitLab / Gitea diff viewersThe web UI for any forge already produces HTML diffs. Use diff2html-cli when you want a self-contained HTML file independent of a forge.
git format-patch + emailThe traditional patch-by-email workflow. Plain text only.
prettydiffOlder, larger renderer. Largely superseded by diff2html.

Common gotchas

  1. Input mode: stdin vs file vs git. With no positional arg, the CLI reads stdin. Pass a .diff / .patch path to read a file. Pass -i command to run a git diff command internally. Mixing modes (e.g. git diff | diff2html some.diff) silently ignores stdin.
  2. --output=preview opens the browser only on the local machine. In CI, this hangs because there's no GUI. Use --output=stdout > out.html or --output=file --file ./out.html in headless environments.
  3. highlight.js languages must be auto-detected. It infers language from filename extension. Diffs against extension-less files (Dockerfile, Makefile) lose syntax colouring unless you preprocess.
  4. Large diffs balloon HTML output. A 10 MB diff can produce a 30 MB HTML file because every changed line embeds inline styles + highlight markup. Split with git diff --stat first to identify oversized files.
  5. Line endings normalise to LF. If the source repo uses CRLF, the rendered HTML displays LF and the line numbers may drift compared to the editor. Set git config core.autocrlf false on Windows or use git diff --text.
  6. CLI flags are case-sensitive. --diffStyle (camelCase) and --style (lowercase) live side-by-side. Use diff2html --help rather than guessing.
  7. No first-class GitHub-PR mode. To diff a PR, fetch the unified-diff URL (https://github.com/<org>/<repo>/pull/<n>.diff) and pipe it via stdin.

Real-world recipes

Pre-PR diff review

Render the working-branch's diff against main into an HTML file the reviewer can open offline. Pair with a Slack / email attachment for async code review.

bash
git diff main...HEAD | diff2html -s side -i stdin -F review.html --hwt none
open review.html

Output: review.html with side-by-side diff for every changed file.

The flags decompose as: -s side (side-by-side), -i stdin (read piped input), -F review.html (output filename), --hwt none (no embedded highlight.js theme — uses default).

CI artifact for pull requests

In GitHub Actions, render the PR diff into an HTML file and upload as an artifact. Reviewers download it from the workflow run page.

yaml
# .github/workflows/diff-html.yml
name: PR diff HTML
on:
  pull_request:
    branches: [main]
jobs:
  diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - run: |
          git fetch origin ${{ github.base_ref }}
          git diff origin/${{ github.base_ref }}...HEAD > pr.diff
      - run: npx diff2html-cli@latest -i file -F pr.html pr.diff
      - uses: actions/upload-artifact@v4
        with:
          name: diff-html
          path: pr.html

Output: every PR produces a downloadable diff-html artifact.

Render a GitHub PR's .diff URL directly

GitHub exposes every PR's diff via a stable URL pattern. Pipe it straight into diff2html without cloning.

bash
curl -sL https://github.com/withastro/astro/pull/12345.diff \
  | diff2html -i stdin -s side -F astro-12345.html

Output: astro-12345.html rendered from the live GitHub diff.

GitLab uses .diff similarly: https://gitlab.com/group/project/-/merge_requests/<n>.diff. Gitea, Forgejo, and Codeberg follow the same convention.

Embed in a static site

--format=json produces machine-readable output you can stitch into a custom page. Useful for review dashboards or doc sites that want diffs without diff2html's default chrome.

bash
git diff HEAD~5 | diff2html -i stdin -f json > diff.json

Output: JSON array with one object per file (path, language, line counts, hunks).

Combined with the npm diff2html library at build time, this powers custom UIs that render diffs alongside your own design system.

Programmatic use via the library

The CLI is a thin wrapper around the diff2html library. For tighter integration (Astro page, React component, server route), call the library directly.

typescript
import { html as renderDiff } from "diff2html";
import { readFile } from "node:fs/promises";

const unifiedDiff = await readFile("change.diff", "utf8");
const htmlOut = renderDiff(unifiedDiff, {
  drawFileList: true,
  matching: "lines",
  outputFormat: "side-by-side",
  highlight: true,
});

console.log(htmlOut);

Output: HTML fragment ready to embed inside a layout's <body>.

The library and CLI follow the same minor-version line — install both at the same version to avoid template drift.

Performance tuning

A 10 MB unified diff against thousands of files takes 30-60 seconds and produces a 30+ MB HTML file. The tuning knobs:

  • --highlightCode=false disables highlight.js. Cuts CPU by ~80% on large diffs. Use when diffs are huge or syntax is irrelevant.
  • --summary=closed collapses the file tree by default; the browser doesn't pre-render every section.
  • --matching=none disables the intra-line matching algorithm (default lines). Cheaper but coarser diff.
  • Split per-file. Pipe git diff --name-only and render one file at a time when the consolidated output is unwieldy.
  • Cache JSON, render late. Generate JSON in CI (cheap), render to HTML at view time (in a serverless function). Avoids storing huge HTML artifacts.
  • Skip binary / generated files. Add a .gitattributes entry *.snap binary so git diff omits them, or post-filter with git diff -- ':!*.lock'.

A quick benchmark on a 5 MB diff (single Astro repo upgrade):

ConfigTimeOutput size
Default18 s21 MB
--highlightCode=false4 s8 MB
--matching=none14 s19 MB
Both3 s7 MB

Production deployment

diff2html-cli is a developer tool — it usually runs in CI or on a workstation, not as a long-lived service. Two deployment patterns appear in practice.

Lockfile-pinned in CI

For reproducibility, install as a dev-dep instead of using npx diff2html-cli@latest (which picks up new majors silently).

json
{
  "devDependencies": {
    "diff2html-cli": "^5.2.16"
  },
  "scripts": {
    "diff:render": "diff2html -i stdin -s side -F dist/pr.html"
  }
}
bash
git diff main...HEAD | npm run diff:render

Output: uses the locally installed binary, consistent across CI runs.

Bundled into a review-bot image

A custom GitHub bot that comments on PRs with a hosted diff URL can build a small Docker image that includes diff2html-cli and a static-file server.

dockerfile
FROM node:20-slim
RUN npm install -g diff2html-cli@5
WORKDIR /work
ENTRYPOINT ["diff2html"]
bash
docker build -t diff-bot .
git diff | docker run --rm -i diff-bot -i stdin -o stdout > review.html

Output: containerised CLI without polluting the host's global node_modules.

Version migration guide

The CLI and library share a release line but ship at slightly different tempos. The CLI usually lags by a minor.

FromToKey change
diff2html-cli@3diff2html-cli@4Default output changed from line-by-line to side-by-side. Reverse with --style line if you relied on the old default.
diff2html-cli@4diff2html-cli@5Node 16+ floor. Internal bundler switched to esbuild. Some legacy templates removed.
diff2html@3diff2html@4Output API changed — Diff2html.html(...) returns a fragment without surrounding <html> chrome.
diff2html@4diff2html@5TypeScript types rewritten; some interfaces renamed. Affects programmatic users.

For long-lived CI scripts, pin the version in package.json or in the npx call: npx diff2html-cli@5.2.16. The unpinned @latest form picks up majors without warning.

ESM/CJS interop & bundling

The CLI itself is a global binary — interop concerns only apply when using the underlying diff2html library programmatically.

SetupPattern
ESMimport { html as renderDiff } from "diff2html";
CJSconst { html: renderDiff } = require("diff2html");
TypeScriptTypes ship in-tree. Set "moduleResolution": "node16" for the "exports" map to work.
Webpack / Vite (front-end)Works — diff2html is browser-compatible (it powers the diff2html.xyz playground). highlight.js is a large dep; tree-shake aggressively.
Cloudflare WorkersLibrary works on Workers. CLI does not (it shells out to git).
BunLibrary works. CLI works via bun x diff2html-cli.
DenoLibrary works via npm:diff2html. CLI works via deno run --allow-all npm:diff2html-cli.

The library bundle is ~150 KB minified before highlight.js. With highlight: true enabled and all languages, the total can exceed 1 MB — load on demand for browser use.

Plugin & ecosystem coverage

PackageRole
diff2htmlThe renderer library — CLI is a thin wrapper. Use directly for embedded diffs.
parse-diffLower-level unified-diff parser. Useful for custom rendering when diff2html's templates don't fit.
diff (jsdiff)Produce diffs in JS (line, char, word). Pipe its output through diff2html for HTML.
gitdiff-parserStricter parse of git diff extensions (file modes, renames). Pairs with custom renderers.
highlight.jsSyntax highlighter. Transitive dep; configure via --hwt (highlight.js whole-theme) and --hl (highlight.js languages).
prismjsAlternative syntax highlighter. Some forks of diff2html swap in Prism for smaller bundles.
delta (Rust, not npm)In-terminal alternative. Sibling tool — use diff2html when HTML matters, delta when you stay in the shell.
git-pull-request-formatter, custom botsCompose around diff2html to post PR previews to chat.

Testing & CI integration

The CLI's output is HTML, which is fiddly to assert against. Snapshot the output or assert against the JSON form.

Snapshot the JSON output

typescript
import { test, expect } from "vitest";
import { execSync } from "node:child_process";

test("renders a small diff", () => {
  const diff = execSync("git diff HEAD~1 HEAD -- src/index.ts").toString();
  const json = execSync("diff2html -i stdin -f json", { input: diff }).toString();
  const parsed = JSON.parse(json);
  expect(parsed[0].language).toBe("typescript");
  expect(parsed[0].blocks.length).toBeGreaterThan(0);
});

Output: 1 passed.

CI rendering job

A common pattern is rendering the diff for every push to main and uploading to a static-site bucket so reviewers have a permalink.

yaml
- run: git diff HEAD~1 HEAD > daily.diff
- run: diff2html -i file -F /tmp/daily.html daily.diff
- run: aws s3 cp /tmp/daily.html s3://review/diffs/$(date -u +%F).html

Security considerations

  • Untrusted diffs render arbitrary HTML? No — diff2html escapes user content. But the CLI runs git diff via -i command mode using shell semantics. Never pass user input through that path: diff2html -i command "git diff; rm -rf /" would execute the rm after the diff. Use -i stdin or -i file for any untrusted source.
  • highlight.js DoS. Very long lines or pathological inputs can stall the highlighter. Set a max line length in your wrapper, or run diff2html --highlightCode=false on diffs larger than a threshold.
  • CSP for embedded output. The CLI emits inline <style> blocks. To embed inside a CSP-strict page, render with --format=json and use your own CSS, or add 'unsafe-inline' to style-src (not recommended).
  • HTML in a code-review email. Mail clients (Outlook, Gmail) strip most styles. Render with --style=line and a minimalist template for cross-client rendering.
  • Disk usage on large repos. A monorepo's full history diff can produce gigabytes of HTML. Cap the input with git diff --max-size=1m or --stat first.

Troubleshooting common errors

Error: ENOENT reading the diff — the file path doesn't exist. With -i file, the path is relative to cwd; pass an absolute path or cd first.

Empty HTML output — no diff in the input (working tree clean, or HEAD~1..HEAD was empty). Check git diff | wc -l first.

Syntax highlighting missing for a recognised languagehighlight.js infers from the filename. Files with no extension (Dockerfile, Makefile, COMMIT_EDITMSG) lose colouring. Preprocess to add an extension, or render with --highlightCode=false for consistency.

--output=preview hangs in headless CI — there's no browser to open. Use --output=stdout > out.html or --output=file --file=out.html.

Side-by-side layout overflows on narrow screens — render with --style=line (line-by-line / unified) for mobile-friendly output, or wrap the embed in a horizontally scrolling container.

Old browser shows blank page — diff2html emits modern HTML/CSS. For IE11 (rare these days), pre-render server-side and serve a static snapshot.

Diff doesn't match what the user sees in GitHub — GitHub shows merge-base diffs (base...head); plain git diff base head is the two-dot form (which excludes commits on base since the branch split). Use git diff base...head (three dots) for parity.

Bin not found after global install — npm's global prefix isn't on PATH. Run npm config get prefix and add <prefix>/bin to PATH, or prefer npx diff2html-cli.

When NOT to use this

Skip diff2html-cli when:

  • In-terminal viewing is enough. delta (Rust) or diff-so-fancy produce richer terminal output without HTML overhead. git config core.pager delta sets it globally.
  • The forge already renders diffs. GitHub / GitLab / Gitea / Bitbucket all have built-in diff viewers with permalinks, comments, and reactions. diff2html shines for off-platform sharing (email, Slack, archived snapshots), not as a replacement.
  • You need IDE-quality diffs. VS Code's diff view, JetBrains' diff tool, and git difftool integrate with the editor and offer in-place editing. diff2html is read-only.
  • The output is fed to another tool. Plain unified diff is the universal interchange format. Wrapping it in HTML adds friction for downstream parsers.
  • Bandwidth is constrained (email-heavy review workflow). A 10 MB diff produces a 30 MB HTML attachment. git format-patch + plain text is still better for that case.
  • You target the browser at scale. For server-rendered diffs on a busy site, use the diff2html library directly and cache the output — the CLI launch cost (~500 ms) is too high per request.

See also