前端时间体验了一个dbt,作为一个国外dataops的践行者,dbt估值已经达到了42亿美元,体验后,从前端的角度来讲,让人印象最深的功能是他们的一个版本管理的Web IDE,也是他们的核心功能之一。
版本管理在dataops中是一大特性,它能够让你管理数据开发像管理代码仓库一样,从而提高开发效率。此外,版本控制不仅仅可以应用于dataops。出于学习和调研的目的,我决定利用业余时间实现一个版本管理WebIDE的最小版。
既然开始做,就需要有一点规划,首先这个项目是一个实验,业余时间的探索,尝试。目标是用于研究版本管理的Web IDE,了解其中的逻辑与相关技术栈。
探索的过程也是思考,学习的过程。
WebIDE一直是前端领域非常重要的部分,个人也比较感兴趣。此外也想借着这个机会学习一下相关的技术,不仅仅是为了未来发展的需要,主动探索学习本身是一个很有意思的事情。
先看一下项目的最终效果吧。以便大家有个整体的认识。

项目名称为dbv。向dbt致敬。
为了简单快捷,该应用同时支持一个极狐GitLab实例,实例地址写入到环境变量中。
第一次进入应用首页,会检查是否已经配置Access Token。没有配置则会弹窗配置,不配置无法使用其他功能。Access Token配置后,会报保存到本地。并请求个人的项目列表。

用户选择个人的项目,然后选择分支,选中分支后,就会加载出该分支的文件树 如下:

每一个文件都可以点击,点就后就会在左侧的编辑器中显示文件内容。

用户修改文件内容后,点击提交,输入提交信息,点击确定就可以将文件更新 到极狐GitLab 仓库。


框架的搭建比较简单,使用的是vite + vue + element plus + axios + monaco-editor
之所以用vite,是因为它快,开发体验尤为重要,不要拖慢别人的研发速度。
之所以没用ts,是因为它慢(其实是菜)
API使用的是极狐GitLab OpenAPI。所以无需后端,只需要在项目中配置一下proxy。
其他的前端技术这里不做过多的介绍,这里稍微介绍一下极狐GitLab OpenAPI。
极狐GitLab 对外提供了OpenAPI,开发者只需要使用一个Access Token,就能调用极狐GitLab所有的对外接口。所有的接口文档。
创建极狐GitLab个人Access Token 可以点击该链接。顺便提一下,在创建Access Token 时,你可以选择API的授权范围。官方提供了7个选项,包括一些只读,读写。

我们项目最近也做了OpenAPI,相对的就做的比较简单。一个token,一巴梭。拿到token就拿到了所有接口的使用权限,这样的设计可能造成很大的安全问题。研究这些优秀的开源产品可能反哺自己产品。
市面上一些插件,触发极狐GitLab CI/CD,获取项目分支,等功能,大部分都是通过Access Token调用OpenAPI来实现的。比如Jenkins拉取项目分支,tag,极狐GitLab 插件。
极狐GitLab OpenAPI提供了很多API,包括获取个人的项目,项目分支,仓库文件,提交单个,多个文件,而接口的风格基本遵循RESTFUL。接口的定义很有语义化,理解,使用简单。
在该项目里使用的接口有这些
const baseVersion = `/api/v4`
import request from "./request"
// 获取项目
export function getProjectsApi (data) {
return request({
url: `${baseVersion}/projects`,
method: 'get',
params: data
})
}
// 获取分支
export function getBranchesApi (projectId, data) {
return request({
url: `${baseVersion}/projects/${projectId}/repository/branches`,
method: 'get',
params: data
})
}
// 获取项目文件
export function getFileTreeApi (projectId, data) {
return request({
url: `${baseVersion}/projects/${projectId}/repository/tree`,
method: 'get',
params: data
})
}
// 获取文件内容
export function getFileConentApi (projectId, filePath, data) {
return request({
url: `${baseVersion}/projects/${projectId}/repository/files/${filePath}`,
method: 'get',
params: data
})
}
// 更新单个文件内容
export function updateOneFileConentApi (projectId, filePath, data) {
return request({
url: `${baseVersion}/projects/${projectId}/repository/files/${filePath}`,
method: 'put',
data
})
}
// 批量提交多个文件内容
export function commitFilesApi (projectId, data) {
return request({
url: `${baseVersion}/projects/${projectId}/repository/commits`,
method: 'post',
data
})
}
在调用API时,发现极狐GitLab OpenAPI的设计确实有很多借鉴的地方。以获取项目文件的接
口为例projects/${projectId}/repository/tree,默认分页,每页获取20条,最大100条。这样做可以很好地在服务器性能与用户体验得到很好地平衡。对于极狐GitLab 本身项目,一个项目有56400个文件,如果一次获取全部将是一个灾难,(而极狐GitLab在这里采用了一个折中的办法)。
这在设计系统时也是值得借鉴的。对于一个不确定选项多少的下拉框,应该更加谨慎地选择应该返回的条目数。
在研究极狐GitLab OpenAPI的同时,研究了极狐GitLab 自带的WebIDE也是必不可少的。模仿,是人类在婴儿时期就具备的技能。
在极狐GitLab 的WebIDE中,使用了未开放的接口。如
全量获取某一commmit下文件json数据https://jihulab.com/gitlab-cn/gitlab/-/files/b501f9208c28f2833a4f27703428d1c34ccd4da7?format=json
再比如获取文件内容的
https://jihulab.com/gitlab-cn/gitlab/-/blob/b501f9208c28f2833a4f27703428d1c34ccd4da7/.rubocop_todo/gitlab/delegate_predicate_methods.yml?format=json&viewer=none
于是得出结论,凡是包含/gitlab/-/xxx的都是极狐GitLab 私有的API。没有对外开放。所以在做WebIDE时,不能直接照抄极狐GitLab IDE的接口调用方式。
可能有的人会认为,这不就是调用极狐GitLab的API获取数据,提交表单吗?没什么技术含量,只要有时间我也能做出来。但就是因为肯不肯花时间拉开了一大批人的认知。
很多人会轻视简单的东西, 但越是简单的东西,设计的越是巧妙,值得学习和研究。
在实践的过程中,真正算的上突破点一共有三个
一是对于vite + vue + element plus 积累了更多的框架搭建经验
二是对于极狐GitLab 文件数组转树的算法实现
极狐GitLab 接口projects/${projectId}/repository/tree返回的数据格式是这样的。
[
{
id: "99bf960e214e73e5513e054ac34c331b6d4b1a46",
mode: "040000",
name: "public",
path: "public",
type: "tree",
},
{
id: "3e5a13962197105f2078d2a224cc57dfa09b4893",
mode: "100644",
name: "index.html",
path: "public/index.html",
type: "blob",
}
]
需要转化成树形数据结构,花了两个小时编写了一个方法
// GitLab 文件列表转树
export function fillArrToTree(arr) {
const len = arr.length
const treeRes = []
const treeMap = {}
for (let i = 0; i < len; i++) {
const item = arr[i]
if (item.type === 'tree') {
item.child = []
}
treeMap[item.path] = item
if (item.path === item.name) {
treeRes.push(item)
} else {
// 解析父子关系
const parentPath = item.path.replace(`/${item.name}`, '')
if (treeMap[parentPath]) {
if (treeMap[parentPath].child) {
treeMap[parentPath].child.push(item)
} else {
treeMap[parentPath].child = [item]
}
}
}
}
return treeRes
}
核心是解析出父子关系,然后将子级挂载在父级下。奈何自己太笨
对于获取文件列表一次只能获取100个,所以使用了一个递归的方法来获取全部文件列表。
此外还有就是获取 件需要对文件路径进行转义,并且获取的文件内容需要进行base64解码。
三是monaco-editor 编辑器的集成。
宇宙第一最强编辑器monaco-editor一直是我想要研究的对象。奈何一直没机会,也不是做这一块的。 这次终于搞了一下。如果有机会可能出一个编辑器的专栏。
其实前端有门槛的核心技能就那么几个,编辑器算是一个。
对于web编辑器的选择,一定要视具体使用场景而选择。并不是一个编辑器适合天下所有业务。
极狐GitLab 的Web IDE用的是monaco-editor, vscode 用的是monaco-editor,dbt的编辑器用的是monaco-editor。
如果你要寻找一个面向开发者的web编辑器,那么monaco-editor 将是一个很不错的选择。
前端有趣的东西有很多,迷茫的同学不如从中选定一个自己感兴趣的专题,如可视化,编辑器,拓扑图,血缘图,低代码。
成长一直都是自己的事情,而学习是最好的投资。与其精神内耗,不如选择自己喜欢的,有价值的,感兴趣,沉浸其中去做,坚持去做,相信总有一天会收获惊喜的。