类似 git 前端代码比对

代码比对

类似于 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 效果:

  1. Diff2Html:直接从库中使用解析器和 html 生成器,可以完全控制如何处理生成的 json 或 html。
  2. Diff2HtmlUI:使用这个包装器可以很容易地在 DOM 中注入 html,并为 diff 添加了一些不错的功能,比如语法高亮。

diff2html-ui 助手

可以简化浏览器中的简单任务的包装器,例如:代码突出显示和 js 效果,提供了更丰富的支持,调用 Diff2html 并集成了 highlight 语法高亮,所以这里我们使用这种方式,此外他还提供了:

  1. 支持 json
  2. 支持文件目录概要显示/隐藏,文件折叠(line-by-line 模式才可用)
  3. 支持收起已查看文件(文件头部右边的 viewed 复选框,side-by-side 模式下会失效)
  4. 提供类似 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;
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值