代码比对
类似于 gitee 选择 对比源和目标,然后获取两个版本的文件目录树,再根据选择的具体文件对比两个文件的内容,一般是对比源相对于目标的文件改动进行差异化展现,在内容上就是 增加、删除、变更
使用的技术
1、diff 版本 5.1.0
2、diff2html 版本:3.4.35(diff2html 中已内置 diff)
diff: diff 是一个基于 javascript 实现的文本内容 diff 的库
diff2html:将差异转化为 html,特点:支持 git 和统一差异、逐行和并排差异、新旧行号、插入和删除的行、GitHub 类似的风格、代码语法高亮、线相似性匹配、轻松选择代码
diff 中有很多方法,我们这里用到的是 createPatch 方法,创建统一的 diff 补丁。
Diff.createPatch(fileName, oldStr, newStr, oldHeader, newHeader);
参数:
- fileName:要在补丁的文件名部分输出的字符串
- oldStr:原始字符串值
- newStr:新的字符串值
- oldHeader:要包含在旧文件头中的附加信息
- newHeader:要包含在新文件头中的附加信息
- options:带有选项的对象。
-
- context:描述了应该包含多少行上下文。
-
- ignoreWhitespace:true 忽略前导和尾随空格。
-
- newlineIsToken:true 将换行字符视为单独的标记。这允许对换行结构的更改独立于行内容而发生,并被视为行内容。一般来说,这是对更友好的 diffLines 形式。
diff2html 中我们用到了两个方法
1、parse 方法,将 diff 字符串转为 JSON 表示的格式
parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[];
2、Diff2HtmlUI,diff2html 提供的一个包装器
new Diff2HtmlUI(target: HTMLElement, diffInput?: string | DiffFile[], config: Diff2HtmlUIConfig = {}, hljs?: HighlightJS)
提供了 2 种方式展示 diff 效果:
- Diff2Html:直接从库中使用解析器和 html 生成器,可以完全控制如何处理生成的 json 或 html。
- Diff2HtmlUI:使用这个包装器可以很容易地在 DOM 中注入 html,并为 diff 添加了一些不错的功能,比如语法高亮。
diff2html-ui 助手
可以简化浏览器中的简单任务的包装器,例如:代码突出显示和 js 效果,提供了更丰富的支持,调用 Diff2html 并集成了 highlight 语法高亮,所以这里我们使用这种方式,此外他还提供了:
- 支持 json
- 支持文件目录概要显示/隐藏,文件折叠(line-by-line 模式才可用)
- 支持收起已查看文件(文件头部右边的 viewed 复选框,side-by-side 模式下会失效)
- 提供类似 gitte 的两种格式化类型,side-by-size(两侧展示)、line-by-line(行内展示)
配置项:
diff2html 配置项
- inputFormat:输入数据的格式, ‘diff’ 或者 ‘json’, 默认是’diff’
- outputFormat:输出数据的格式, ‘line-by-line’ 或者 ‘side-by-side’, 默认是’line-by-line’
- showFiles:在对比之前查看文件列表, true 或者 false, 默认是 false
- matching:匹配 level, 'lines’用于匹配行, ‘words’ 用于匹配行和单词,或者设置为 ‘none’, 默认为 none
- matchWordsThreshold:单词相似度下限, 默认是 0.25
- matchingMaxComparisons:为了匹配一组变化最多执行的比较次数,默认是 2500
- maxLineLengthHighlight:如果行数小于此值,则仅仅执行差异突出显示,默认是 10000
- templates:使用预备好的编译的模板替换部分 html 的对象
- rawTemplates:具有原始未编译模板的对象替换部分 html
更多信息关于可能的模板请查看 src/templates
Diff2HtmlUI 助手配置项,上面的配置项在 Diff2HtmlUI 里面同样有效
- synchronizedScroll:以并排模式滚动两个窗格, true 或 false,默认为 true
- highlight:语法高亮显示 diff 上的代码:true 或 false,默认值为 true
- fileListToggle:允许切换文件摘要列表, true 或 false,默认为 true
- fileListStartVisible:选择文件摘要列表是否开始可见, true 或 false,默认值为 false
- fileContentToggle:允许切换每个文件的内容, true 或 false, 默认为 true
- stickyFileHeaders:使文件头具有粘性, true 或 false, 默认为 true
使用方式
依赖引入:
import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-slim.js";
import "highlight.js/styles/github.css";
import "diff2html/bundles/css/diff2html.min.css";
初始化:
const targetElement = document.getElementById("destination-elem-id");
const configuration = { drawFileList: true, matching: "lines" };
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);
// or
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffJson, configuration);
绘制:
diff2htmlUi.draw();
diff2htmlUi.highlightCode();
封装
上述的使用是先已经得到了差异化字符串,例如这种格式:
`diff --git a/sample.js b/sample.js
index 0000001..0ddf2ba
--- a/sample.js
+++ b/sample.js
@@ -1 +1 @@
-console.log("Hello World!")
+console.log("Hello from Diff2Html!")`
但是我们实际使用的时候往往是先得到两段文本,由文本生成差异字符串,这里要先使用 diff 来获取文本差异
1、获取文本差异
import { createPatch } from "diff";
import { parse } from "diff2html";
let diffJsonList = [];
let { fileName, oldHeader, newHeader, prevData, newData } = diffFileData;
let oldString = prevData || "";
let newString = newData || "";
let args = [
fileName || "",
oldString,
newString,
oldHeader || "",
newHeader || "",
{ context:99999 },
];
// 对比差异
const diffStr = createPatch(...args);
// 差异json化
const diffJson = parse(diffStr);
diffJsonList.push(diffJson[0]);
2、将文本差异使用 diff2htmlUi 插入 html 中
// 使用diff2html-ui
const targetElement = document.getElementById(id);
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffJsonList, configuration);
diff2htmlUi.draw(); //绘制页面
diff2htmlUi.highlightCode();
最终组件代码示例:
/**
diffFileData 对比文件数据
{
prevData: any(string、json), // 旧数据
newData: any(string、json), // 新数据
fileName?: string, // 文件名
oldHeader?: string, // 重命名,旧文件名,需要带后缀如aaa.js 否则不能语法高亮
newHeader?: string // 重命名,新文件名, 需要带后缀如aaa.js 否则不能语法高亮
headerInfo ?: domString // 自定义头部,有headerInfo可以自定义文件名后面的内容
},
outputFormat diff格式,line-by-line || side-by-side
id Diff2HtmlUI 挂载html的id,多实例的情况下,各个实例需要唯一id,防止页面冲突
*/
import React, { useCallback, useEffect } from "react";
import { createPatch } from "diff";
import { parse } from "diff2html";
import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-slim.js";
import "highlight.js/styles/github.css";
import "diff2html/bundles/css/diff2html.min.css";
import styles from "./index.module.scss";
const CodeDiff = ({ diffFileData, outputFormat, id }) => {
const createDiffData = useCallback(() => {
let diffJsonList = [];
let { fileName, oldHeader, newHeader, prevData, newData } = diffFileData;
let oldString = prevData || "";
let newString = newData || "";
let args = [
fileName || "",
oldString,
newString,
oldHeader || "",
newHeader || "",
{ context: 99999 },
];
// 对比差异
const diffStr = createPatch(...args);
// 差异json化
const diffJson = parse(diffStr);
diffJsonList.push(diffJson[0]);
// 自定义头部,有headerInfo可以自定义文件名后面的内容
const customizationHeader = diffFileData.headerInfo
? {
"generic-file-path": `
<div class="code-diff-header">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg>
<span class="d2h-file-name">${diffFileData.fileName}</span>
${diffFileData.headerInfo}
</div>`,
}
: {};
// 配置项
const configuration = {
outputFormat: outputFormat || "side-by-side",
drawFileList: false, // 在diff之前显示文件列表(左上角 Files changed(1)show)
stickyFileHeaders: true, // 使文件头具有粘性
fileListToggle: false, // 允许切换文件摘要列表
fileListStartVisible: false, // 选择文件摘要列表是否开始可见
fileContentToggle: false, // 允许切换每个文件的内容(右上角的 viewed 勾选框)
matching: "lines",
synchronisedScroll: true, // 在横向并排的模式中两个面板都使用滚动条
highlight: true,
renderNothingWhenEmpty: true, // 如果diff在比较中没有显示任何变化,则不渲染
// 具有原始未编译模板的对象替换部分html
rawTemplates: {
// "tag-file-changed": "",
...customizationHeader,
},
// 扩展名到语言名称的映射,用于突出显示。这将覆盖基于文件扩展名的默认语言检测
highlightLanguages: {
vue: "javascript",
},
};
// 使用diff2html-ui
const targetElement = document.getElementById(id);
const diff2htmlUi = new Diff2HtmlUI(
targetElement,
diffJsonList,
configuration
);
diff2htmlUi.draw(); //绘制页面
diff2htmlUi.highlightCode();
}, [id, outputFormat, diffFileData]);
useEffect(() => {
createDiffData();
}, [diffFileData, createDiffData]);
return (
<div className={styles["diff-ui-component"]} id={id || "code-diff-ui"} />
);
};
export default CodeDiff;
对应的 css
.diff-ui-component {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
// border: 1px solid #c00;
:global {
.d2h-wrapper {
flex: 1;
overflow: hidden;
.d2h-diff-table td {
height: 20px;
line-height: 20px;
padding-top: 0;
padding-bottom: 0;
font-size: 12px;
/* .d2h-code-line-ctn {
white-space: normal;
} */
}
.d2h-file-wrapper {
height: 100%;
overflow: auto;
position: relative;
.d2h-file-header {
display: flex;
align-items: center;
.code-diff-header {
display: flex;
align-items: center;
.d2h-file-name {
margin-right: 16px;
}
}
position: sticky;
top: 0;
z-index: 100;
}
}
}
}
::-webkit-scrollbar-track {
background: #f5f7f9;
}
::-webkit-scrollbar-thumb {
background: #a3a1a1;
}
::-webkit-scrollbar {
width: 6px;
height: 8px;
}
}