背景
笔者目前的工作中会维护内部的系统,其中之一就包括了自建的 Code Review 平台,其中必不可少的功能就是Diff显示,因此需要一种通用的方案将git --diff的输出转换为网页视图,同时需要具有以下功能:
- 代码高亮
- 支持多种视图,
split view/unified view - 支持只传递
diff信息或者同时传递diff信息和原始文件信息,不同之处是如果传递了原始文件信息可以支持Diff View的折叠功能,和现有的Github一致 - 评论等组件支持
- 完整的服务端渲染支持
- 可以接受的性能
- ...
现状
目前的平台直接简单粗暴使用了 diff2html 这个现成的工具,再加上额外做了一些扩展,基本上还算能满足使用需求,但是存在一些致命问题:
- 代码高亮功能简陋,并且没有上下文语义导致如多行注释等常见语法高亮错误,十分影响使用体验
- 由于是原生
html模版的技术方案,导致对其进行功能扩展比较困难,缺失的功能只能采用操作dom的方式进行,因此服务端渲染无法很方便的完成 - 不支持
Diff View的折叠展开以及评论等功能,只能单纯的查看diff - 默认样式比较丑...
查找了一番,并没有找到一个可以完美解决以上需求的工具,因此笔者最终自己开源了一个工具
技术细节
git --diff的视图化大致分为以下几部分:
- 解析。这一部分笔者参考了
Github桌面端软件 (Desktop) 的解析部分,保证解析结果的准确性 - 语法高亮。这一部分笔者采用了 lowlight,一个不依赖
dom的高亮方案,将结果输出为ast,同时该ast满足 hast 规范,这意味着可以使用更强大的高亮引擎如 shiki 来实现更多语法支持,并且能实现完美的SSR支持 - 折叠展开。这一部分需要将
ast和原本的文件内容进行对应,通过对ast进行二次修改,插入行号、范围等信息来实现 - 评论等组件支持。通过内建的
api来实现,暴露出相应的接口,支持事件/组件的触发方式 - 性能。通过对整个流程进行深入优化,尽量减少不必要的组件重渲染来达到最佳的性能(内部采用了笔者开源的另一个状态管理工具 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
/>;
最终效果
- 默认效果
- 代码不换行
- 融合视图
项目地址
Github: git-diff-view
Demo: demo
npm: @git-diff-view
该项目主要分为以下部分:
@git-diff-view/core纯数据处理部分,可以在node/worker中使用,更有利于性能@git-diff-view/reactreact组件,开箱即用@git-diff-view/vuevue组件,开箱即用@git-diff-view/lowlight内置的高亮引擎,由highlight.js驱动@git-diff-view/shiki额外提供的高亮引擎,由shiki驱动@git-diff-view/file支持纯文件内容diff显示,生成diffFile后可用于react/vue组件中
详细用法请参阅Github: git-diff-view
更多请参考: react demo / vue demo