超方便的`git diff`渲染组件,支持 React / Vue,就像Github一样

1,713 阅读4分钟

背景

笔者目前的工作中会维护内部的系统,其中之一就包括了自建的 Code Review 平台,其中必不可少的功能就是Diff显示,因此需要一种通用的方案将git --diff的输出转换为网页视图,同时需要具有以下功能:

  1. 代码高亮
  2. 支持多种视图,split view / unified view
  3. 支持只传递diff信息或者同时传递diff信息和原始文件信息,不同之处是如果传递了原始文件信息可以支持Diff View的折叠功能,和现有的Github一致
  4. 评论等组件支持
  5. 完整的服务端渲染支持
  6. 可以接受的性能
  7. ...

现状

目前的平台直接简单粗暴使用了 diff2html 这个现成的工具,再加上额外做了一些扩展,基本上还算能满足使用需求,但是存在一些致命问题:

  1. 代码高亮功能简陋,并且没有上下文语义导致如多行注释等常见语法高亮错误,十分影响使用体验
  2. 由于是原生html模版的技术方案,导致对其进行功能扩展比较困难,缺失的功能只能采用操作dom的方式进行,因此服务端渲染无法很方便的完成
  3. 不支持Diff View的折叠展开以及评论等功能,只能单纯的查看diff
  4. 默认样式比较丑...

查找了一番,并没有找到一个可以完美解决以上需求的工具,因此笔者最终自己开源了一个工具

技术细节

git --diff的视图化大致分为以下几部分:

  1. 解析。这一部分笔者参考了Github桌面端软件 (Desktop) 的解析部分,保证解析结果的准确性
  2. 语法高亮。这一部分笔者采用了 lowlight,一个不依赖dom的高亮方案,将结果输出为ast,同时该ast满足 hast 规范,这意味着可以使用更强大的高亮引擎如 shiki 来实现更多语法支持,并且能实现完美的SSR支持
  3. 折叠展开。这一部分需要将ast和原本的文件内容进行对应,通过对ast进行二次修改,插入行号、范围等信息来实现
  4. 评论等组件支持。通过内建的api来实现,暴露出相应的接口,支持事件/组件的触发方式
  5. 性能。通过对整个流程进行深入优化,尽量减少不必要的组件重渲染来达到最佳的性能(内部采用了笔者开源的另一个状态管理工具 reactivity-store

用法

尽量保证简洁的使用方式,方便上手

// React

import "@git-diff-view/react/styles/diff-view.css";
import { DiffView } from "@git-diff-view/React";

<DiffView
  
  /* custom class */
  className

  /* return a valid react element to show the widget, this element will render when you click the `addWidget` button in the diff view */

  renderWidgetLine={({ onClose, side, lineNumber }) => jsx.element}

  /* the diff data need to show, type `{ oldFile: {fileName?: string, content?: string}, newFile: {fileName?: string, content?: string}, hunks: string[] }`, you can only pass hunks data, and the component will generate the oldFile and newFile data automatically */

  data={data[v]}

  /* also support the outside `diffFile` to improve performance, so you can use `webWorker` to generate the diff data first, and then pass it to the component */

  diffFile={diffFileInstance}

  /* a list to store the extend data to show in the `Diff View` */
  extendData={extend}

  /* used to render extend data */

  renderExtendLine={({ data }) => jsx.element}

  /* diffView fontSize */

  diffViewFontSize={fontSize}

  /* syntax highlight */

  diffViewHighlight={highlight}

  /* diffView mode: SplitView / UnifiedView */

  diffViewMode={mode}

  /* diffView wrap: code line auto wrap */

  diffViewWrap={wrap}

  /* enable `addWidget` button */

  diffViewAddWidget

  /* EventListener when the `addWidget` button clicked */

  onAddWidgetClick
/>;

最终效果

  1. 默认效果

image.png

  1. 代码不换行

image.png

  1. 融合视图

image.png

项目地址

Github: git-diff-view

Demo: demo

npm: @git-diff-view

该项目主要分为以下部分:

  1. @git-diff-view/core 纯数据处理部分,可以在node/worker中使用,更有利于性能
  2. @git-diff-view/react react 组件,开箱即用
  3. @git-diff-view/vue vue 组件,开箱即用
  4. @git-diff-view/lowlight 内置的高亮引擎,由highlight.js驱动
  5. @git-diff-view/shiki 额外提供的高亮引擎,由shiki驱动
  6. @git-diff-view/file 支持纯文件内容diff显示,生成diffFile后可用于react/vue组件中

详细用法请参阅Github: git-diff-view

更多请参考: react demo / vue demo