在日常的代码评审、配置变更对比、版本发布记录等场景中,我们经常需要将 Git Diff 内容以直观的方式展示在页面上。纯文本 diff 可读性差,直接嵌入 Monaco 等编辑器又过于笨重且加载慢。近期在项目中调研了主流的前端 Diff 展示方案,最终选型 diff2html 落地,在这里把选型思路和实战代码分享给大家。
一、主流方案横向对比
目前前端常见的 Diff 展示方案主要分为三类,各有适用场景:
1. 编辑器类方案:Monaco Editor / CodeMirror
这类是完整的代码编辑器内核,代表是 VS Code 底层的 Monaco,功能最强,支持代码编辑、完整 IDE 交互体验。但缺点也很明显:
- 包体积极大(数MB级别),首屏加载慢,内存占用高;
- Diff 只是附属功能,文件导航、变更统计、折叠等特性需要大量二次开发;
- 几乎无法支持服务端渲染(SSR)。
适合场景:需要在线编辑代码的重度交互场景,纯展示场景性价比很低。
2. 框架绑定组件:如 react-diff-view
针对特定前端框架封装,接入简单。但存在明显局限:
- 技术栈强绑定,无法跨项目复用;
- 样式完整性一般,很多展示细节需要自行补全;
- 生态成熟度有限,对特殊格式 Diff 的兼容性一般。
适合场景:单一技术栈、需求简单的轻量场景。
3. 纯语法高亮:highlight.js / Prism.js
最轻量,仅做基础的红绿行染色。但能力非常有限:
- 只有行内着色,没有分栏对比、行对齐、文件导航等专业 Diff 能力;
- 不支持多文件 Diff 自动解析与分组;
- 没有差异匹配逻辑,长文本可读性提升有限。
适合场景:极短 Diff 片段的简单展示。
二、为什么选择 diff2html
diff2html 是 Diff 渲染领域的老牌事实标准,它最大的特点是在功能完整性、轻量性、通用性之间做到了极佳的平衡,非常适合纯展示类业务场景。
核心优势
- 轻量高效,无冗余消耗 核心库仅几十 KB,加上语法高亮依赖也不到 200KB,相比 Monaco 体积小一个量级,首屏加载速度快。没有编辑器复杂的实例管理和内存开销,也不存在内存泄漏风险,适合列表页、详情页等批量渲染场景。
- 原生兼容 Git Diff,开箱即用 直接输入标准
git diff原始文本即可自动解析,不需要手动拆分新旧两份代码。原生自带文件列表导航、变更行数统计、变更块折叠、分栏/行内双模式切换等 Diff 专属功能,无需二次开发。 - 框架无关,全场景通用 不绑定任何前端框架,React、Vue、原生 JS 都能直接使用,跨技术栈项目复用零成本。输出是标准 HTML+CSS,不绑定组件生命周期,接入成本极低。
- SSR 友好,首屏性能佳 本质是「输入 diff 文本 → 输出 HTML 字符串」的纯函数,不需要浏览器环境,在 Next.js 等 SSR 场景可以直接在服务端生成 HTML 直出,首屏体验远优于客户端渲染的编辑器方案。
- 生态成熟,生产级稳定 经过多年生产环境验证,对多文件、文件重命名、特殊字符等各种边缘 Diff 格式兼容性极强。语法高亮复用 highlight.js 生态,切换明暗主题仅需替换一行 CSS。
三、React + TypeScript 实战封装
下面给大家一个可直接复制使用的 TS 版封装组件,内置常用优化配置。
1. 安装依赖
npm install diff2html highlight.js
2. 通用 Diff 展示组件(DiffViewer.tsx)
typescript
import React, { useEffect, useRef } from 'react';
import * as Diff2Html from 'diff2html';
import 'diff2html/bundles/css/diff2html.min.css';
import 'highlight.js/styles/github.css';
// 自动提取官方配置类型,随版本自动对齐
type Diff2HtmlOptions = Parameters<typeof Diff2Html.html>[1];
interface DiffViewerProps {
/** 标准 Git Diff 文本 */
diffText: string;
/** 自定义配置,覆盖默认值 */
options?: Partial<Diff2HtmlOptions>;
}
const DiffViewer: React.FC<DiffViewerProps> = ({ diffText, options = {} }) => {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current || !diffText) return;
const defaultOptions: Diff2HtmlOptions = {
outputFormat: 'side-by-side', // 分栏对比模式(GitHub 同款)
drawFileList: true, // 显示顶部文件变更列表
matching: 'lines', // 整行级差异匹配
diffStyle: 'word', // 行内差异按单词标记
};
const mergedOptions = { ...defaultOptions, ...options };
containerRef.current.innerHTML = Diff2Html.html(diffText, mergedOptions);
}, [diffText, options]);
return <div ref={containerRef} style={{ width: '100%', overflowX: 'auto' }} />;
};
export default DiffViewer;
3. 业务页面调用示例
javascript
import React, { useState } from 'react';
import DiffViewer from './DiffViewer';
const CodeReviewPage: React.FC = () => {
const [viewMode, setViewMode] = useState<'side-by-side' | 'line-by-line'>('side-by-side');
// 直接传入 git diff 原始文本即可
const diffContent = `diff --git a/pom.xml b/pom.xml
index 4a94c04..36a3a38 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,7 +79,7 @@
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
- <version>1.7.1</version>
+ <version>1.6.1</version>
</dependency>
`;
return (
<div style={{ padding: 24 }}>
<h3>依赖版本变更</h3>
<div style={{ marginBottom: 12, gap: 8, display: 'flex' }}>
<button onClick={() => setViewMode('side-by-side')}>分栏视图</button>
<button onClick={() => setViewMode('line-by-line')}>行内视图</button>
</div>
<DiffViewer diffText={diffContent} options={{ outputFormat: viewMode }} />
</div>
);
};
export default CodeReviewPage;
四、选型总结与建议
- 如果核心诉求是展示代码变更、提升评审可读性,不需要在线编辑能力,diff2html 是目前综合体验最优的选择;
- 如果需要可编辑的在线代码对比能力,优先选择 Monaco Editor;
- 如果只是简短 Diff 片段的轻量展示,可以直接使用 highlight.js 的 diff 语法高亮。
以上就是本次的分享,组件代码可以直接复用,大家有更好的方案或者使用问题欢迎在评论区交流~