首页
/ Gitea 前端开发指南:Vue 3 与 Go 模板混排的架构、编码规范与 fetch-action 请求框架

Gitea 前端开发指南:Vue 3 与 Go 模板混排的架构、编码规范与 fetch-action 请求框架

2026-09-05 14:08:35作者:田桥桑Industrious

本文基于 Gitea 仓库的 前端开发规范文档 展开,系统讲解 Gitea 前端“Go HTML 模板 + Vue 3 + 硬分叉 Fomantic-UI + Tailwind CSS”的多层技术栈如何组织,逐条解读 Gitea 特有的命名、CSS、TypeScript 与 DOM 操作约定,并结合仓库源码深入剖析 fetch.ts 请求封装与 fetch-action 声明式请求框架的实现机制。读完后你将掌握在 Gitea 前端中新增功能、提交表单、操作 DOM 的标准做法与底层原理。

一、总体架构:四层前端技术如何共存

Gitea 的前端并不是单一框架的纯血实现,而是四种技术协同工作的混合体(引自 guidelines-frontend.md):

  • Vue 3:负责复杂、强交互的页面组件;
  • Fomantic-UI:基于 jQuery 的 UI 框架。Gitea 对其做了硬分叉(hard-fork)并 vendored 了一个经过大量修改的特定版本,处于逐步弃用状态;
  • Tailwind CSS:以工具类方式提供原子化样式;
  • Go HTML 模板:负责所有页面的服务端渲染。

这些技术栈的存在可以从 package.json 中得到印证:vue: 3.5.41tailwindcss: 3.4.19jquery: 4.0.0(供 Fomantic 使用)、eslint-plugin-vuevue-tsc(Vue 工具链),而页面 HTML 则由 Go 侧的 templates/ 目录提供。

源码目录布局

前端源码集中在以下几个目录,这也是规范文档明确列出的结构:

目录 职责
web_src/css/ CSS 样式
web_src/js/ JavaScript 与 TypeScript 源码
web_src/js/components/ Vue 组件
web_src/js/features/ 页面加载时挂载的功能模块
templates/ Go HTML 模板

从构建配置看(vite.config.ts),Vite 以 web_src/js/index.ts 为主入口,另构建 swaggerexternal-render-frontenduser-events.sharedworkerdevtest(开发用 UI 组件画廊样式)以及各主题 CSS 等独立入口,产物输出到 public/assets,并生成 manifest.json。开发模式下 Vite dev server 默认监听 3001 端口,通过端口文件让 Go 服务器发现并代理(见 vite.config.ts),这也是 appType: 'custom' 的原因——所有 HTML 都由 Go 服务,Vite 不处理 HTML

二、依赖管理:pnpm 与“只引用已发布版本”

前端依赖统一由 pnpm 管理。package.json 中声明了:

  • packageManager: pnpm@11.22.0
  • engines 要求 node >= 22.18.0pnpm >= 11.0.0

规范文档规定,前端依赖遵循与后端依赖相同的治理规则,只是相关文件换成了 package.jsonpnpm-lock.yaml,并且有一条硬性要求:新版本号必须始终引用一个已发布的现存版本(published version)——不允许在 lockfile 里锁定尚未正式发布的构建。

构建侧对应 Makefilefrontend 目标(约 L493),它调用 Vite 完成 CSS/JS 产物生成;生产构建还会经过 vite.config.ts 中的 licensePlugin,把 Go 侧与 npm 侧的开源许可文本合并输出为 licenses.txt,并要求许可证属于允许清单(Apache-2.0、MIT、BSD 等)。

三、框架使用:推荐组合与明确红线

规范文档的核心立场是:随意混搭框架会让代码难以维护。推荐的技术组合只有三种:

  1. Vue 3(复杂交互);
  2. 原生 JavaScript
  3. Fomantic-UI(jQuery)——已弃用,但仍是大量存量代码的视觉与行为基础。

由此衍生的几条红线:

  • 避免 Vue 与 Fomantic-UI 混用在同一个组件里;但 Vue 组件可以复用 Fomantic-UI 的 CSS 类来保证视觉一致;
  • 简单页面或与 SEO 相关的页面用 Go 模板渲染,复杂交互页面才上 Vue;
  • Gitea 使用 Vue 3 且刻意不使用 JSX,让 HTML 与 JavaScript 保持分离;
  • 可访问性提示:Fomantic-UI 并非对辅助功能友好的框架,Gitea 只是修补了部分 ARIA 行为,可访问性建设仍在进行中——应优先使用语义化 HTML,并在可行处测试键盘/读屏器行为

四、Gitea 特有编码约定

以下是 guidelines-frontend.md 列出的项目级约定,每一条都直接影响代码评审结果:

  • 功能自包含:每个功能放在自己的文件或目录中,避免巨型杂烩模块;
  • 命名规则:HTML 的 id 和 class 使用 kebab-case,且只带 2~3 个描述功能的关键词;
  • 类名前缀:为类加前缀,避免不同框架间短名称冲突;
  • .field 自动关联:Fomantic 框架可以自动关联作为 .field 元素子节点的 inputlabel,因此通常不需要写 id/for 属性(除非有特定理由);
  • 样式覆盖方式:覆盖框架样式时,新建一个类名而不是直接改框架自己的类;如果要根治,则去修框架源码以覆盖所有场景;
  • 语义化优先:优先用 <button> 等语义元素,而不是泛泛的 <div>
  • 慎用 !important:必须使用时要写明理由;
  • 自定义 DOM 事件加 ce- 前缀,与浏览器原生事件和其他库的事件区分开。

五、CSS 体系:tw-gt-g- 三套前缀的分工

Gitea 的 CSS 约定可以概括为三层:

  1. Tailwind 工具类,前缀 tw-;优先用 flex-* 布局辅助类,而不是给每个子元素手写 margin;
  2. gt- 前缀:Gitea 的 Tailwind 风格通用辅助类;
  3. g- 前缀:框架级私有样式辅助类。

gt-g- 的定义集中放在 web_src/css/helpers.css,文件头部注释即写明:

/*
Gitea's tailwind-style CSS helper classes have `gt-` prefix.
Gitea's private styles use `g-` prefix.
*/

其中包含 .gt-ellipsis(单行省略)、.not-mobile / .only-mobile(按 767.98px 断点显隐)、.tab-size-* 系列、.interact-fg / .interact-bg(交互态颜色)等。只有在 Tailwind 没有对应工具类时,才使用这些自定义辅助类。

Tailwind 的关键配置

tailwind.config.ts 揭示了若干与“多框架混排”直接相关的设计决策:

  • prefix: 'tw-':所有工具类强制加前缀,避免与 Fomantic 类名撞车;
  • important: true:注释写明“框架混在一起,Tailwind 需要能覆盖其他框架的样式”;
  • blocklist 中移除了原生 hidden,因为Gitea 用双类名 .tw-hidden 获得更高优先级(见下方插件);
  • 自定义插件中定义了 .hidden.hidden { display: none }(即 tw-hidden.tw-hidden),并逐条注释了为什么不能用 [hidden] 属性(打不过 display: flex)、不能用原生 .hidden(被 Fomantic 污染)、不能用内联 style="display:none"(难以微调)、也不能用 jQuery 的 show/hide/toggle(对 display: xxx !important 无效);
  • content 扫描范围不仅包含模板与前端源码,还包含 build/models/modules/routers/services 下的 .go 文件——因为 Go 代码中(包括测试文件)也会出现 class="..." 字符串,Tailwind 需要据此生成类;
  • 颜色 token(如 tw-bg-primary)来自 web_src/css/themes/theme-gitea-light.csstheme-gitea-dark.css:root 定义的 --color-* CSS 变量,由构建时自动抽取(tailwind.config.ts),因此暗色主题切换无需重编译样式。

模板中的 class 书写方式

规范要求:在模板中把 class 属性写成一个可整体阅读的单位,例如:

<div class="flex-text-inline {{if .IsFoo}}tw-hidden{{end}}"></div>

而不是把条件类拆分到多处拼接。

六、TypeScript 风格规则

guidelines-frontend.md 对 TypeScript 给出三条明确规则:

  • 类型导入一律用 import type
  • 已知必然存在的值用 ! 非空断言,而不是用 ?. / ?? 掩盖确定的事实(例如 fetch-action.tsel.getAttribute('data-url')! 的写法就是这一约定的体现);
  • 函数只有真正 await 或返回 Promise 才标记 async;避免异步事件监听器,无法避免时,必须在第一个 await 之前调用 e.preventDefault()——否则默认行为(如表单提交、链接跳转)会在异步回调执行前抢先发生。

七、数据获取:fetch.ts 封装与 fetch-action 框架

7.1 底层封装:web_src/js/modules/fetch.ts

规范明确要求:不要直接使用裸 fetch,而是使用 web_src/js/modules/fetch.ts 导出的 GETPOSTPUTPATCHDELETE 包装函数。该文件只有 33 行,核心逻辑是:

export function request(url: string, {method = 'GET', data, headers = {}, ...other}: RequestOpts = {}): Promise<Response> {
  let body: string | FormData | URLSearchParams | undefined;
  let contentType: string | undefined;
  if (data instanceof FormData || data instanceof URLSearchParams) {
    body = data;
  } else if (isObject(data) || Array.isArray(data)) {
    contentType = 'application/json';
    body = JSON.stringify(data);
  }
  headers = new Headers(headers);
  if (!headers.has('content-type') && contentType) {
    headers.set('content-type', contentType);
  }
  return fetch(url, {method, headers, ...other, ...(body && {body})});
}

export const GET = (url: string, opts?: RequestOpts) => request(url, {method: 'GET', ...opts});
export const POST = (url: string, opts?: RequestOpts) => request(url, {method: 'POST', ...opts});
// PATCH / PUT / DELETE 同理

它的价值在于:把 data 选项统一转换为请求体——FormData/URLSearchParams 原样透传(浏览器自动设置正确的 Content-Type),普通对象/数组则自动 JSON.stringify 并设置 application/json 头,调用方无需关心序列化细节。文件顶部注释也说明这是 eslint 规则中唯一被豁免使用裸 fetch 的位置。

7.2 声明式请求框架:web_src/js/modules/fetch-action.ts

对于表单提交、按钮点击和一般网络请求,规范推荐优先使用 web_src/js/modules/fetch-action.ts 框架,理由是它提供一致的 UX 与错误处理。从源码看(initGlobalFetchAction),这是一套“类 HTMX”的声明式系统,元素上的属性即行为配置:

属性 含义
data-fetch-url 请求目标 URL
data-fetch-method HTTP 方法。fetch 触发默认 GETlink-action 元素默认 POST;表单则忽略该属性,以表单 method 为准(缺省 GET
data-fetch-trigger 触发时机:clickchange(用户触发)、load(页面加载)、every 5s(定时,也支持 ms 单位)、fetch-reload(仅在 fetch-sync 成功后由内部触发,用于刷新过时内容)
data-fetch-indicator 加载指示元素选择器,语法与 data-fetch-sync 相同;用户触发默认 $this(元素自身),按钮会直接 disabled,其他元素加 is-loading
data-fetch-sync 响应为 HTML 时的页面更新伪选择器命令(见下)
data-modal-confirm 动作前弹出确认框;可以是提示文本,也可以是 #modal-id 引用已有模态框,并支持 -header-content 变体

两个开箱即用的语义化 class:

  • .link-action<a class="link-action" data-url="...">data-fetch-trigger="click" data-fetch-method="post" data-fetch-url="..." data-fetch-indicator="$this" 的简写(源码 performLinkFetchAction 中注释明确说明);
  • .form-fetch-action:委托监听 submit 事件,自动读取表单 method/action,组装 FormData,并把 submitter 按钮的 name/value 追加进表单数据(prepareFormFetchActionOpts),GET 表单还会把数据拼进 URL query。

data-fetch-sync 伪选择器命令execPseudoSelectorCommands)支持:

  • $this:用响应替换当前元素(outerHTML);
  • $innerHTML:只替换当前元素的 innerHTML;
  • $morph:用 Idiomorph 的 morph 算法做 DOM 差量更新,保留已有 DOM 状态;
  • $body #the-id$closest(tr) td:从 body 逐层查询,或用 closest 灵活定位。

响应处理链

  • 成功且响应为 JSON 时,若包含 redirect 字段则跳转(含 # 或开发模式下走后端 /-/fetch-redirect 中转以绕过浏览器对 location 的限制,见 fetchActionDoRedirect),否则刷新页面;
  • 失败时解析 JSON 中的 errorMessage 弹出 toast 错误提示,并支持 errorFields 把表单对应 .field 节点标红(handleFetchActionErrorFields);
  • 请求头统一带上 X-Gitea-Fetch-Action: 1,后端可据此区分 fetch 请求(如返回 JSON 错误而非整页错误)。

这套框架同样服务于 UI 组件画廊页面 templates/devtest/fetch-action.tmpl,供开发时手工验证各类触发与同步行为。

八、DOM 属性与显隐控制

读取属性:禁用 node.dataset

规范要求避免使用 node.dataset,原因是其 camelCase 转换行为(data-fetch-url 变成 fetchUrl)容易出错;新代码一律使用 node.getAttribute('data-...')。fetch-action 源码本身也全部遵循这一写法。另一条红线:绝不把用户提供的数据直接绑定到 DOM 节点上(防止 XSS 面扩大)。

显示/隐藏元素

  • Vue 中v-if / v-show;若元素内含不受 Vue 管理的 DOM(例如第三方插件内部结构),必须用 v-show,避免 v-if 销毁节点导致 DOM 状态丢失;
  • Go 模板和原生 JS 中,统一使用 .tw-hidden 类配合 web_src/js/utils/dom.ts 的三个辅助函数:
export function toggleElem(el: ElementArg, force?: boolean): ArrayLikeIterable<Element> {
  return toggleElemClass(el, 'tw-hidden', force === undefined ? force : !force);
}
export function showElem(el: ElementArg) { return toggleElem(el, true); }
export function hideElem(el: ElementArg) { return toggleElem(el, false); }

这与 tailwind.config.ts 的注释是配套的:.tw-hidden(双类名提权)必须能压过一切 display: xxx !important,是 Gitea 中唯一被认可的隐藏手段。

九、UI 组件画廊:/devtest 页面

开发模式下,Gitea 提供标准化的 UI 组件画廊,访问 /devtest(如 http://localhost:3000/devtest),集中预览各类组件的当前渲染效果;这些页面同时被 e2e 测试复用tests/e2e/)。

从源码看,该路由组定义在 routers/web/web.gom.Group("/devtest", ...)),处理器位于 routers/web/devtest/ 包:

  • devtest.goList 会遍历 templates/devtest/ 目录列出所有组件模板页(如 form-fieldsfomantic-dropdowntoast-and-messagerelative-time 等),TmplCommon{sub} 参数渲染对应模板;
  • 此外还提供 fetch-action-testmail-preview 以及 Actions 运行视图的 mock 数据(mock_actions.go)等专门测试端点。

这构成了一条“改样式/组件 → 在 /devtest 目检 → e2e 自动回归”的闭环,是验证前端改动的第一现场。

十、小结

Gitea 的前端规范本质上是一套“在框架混排中维持秩序”的工程约定:

  • 架构上,Go 模板保底(简单/SEO 页面),Vue 3(无 JSX)承接复杂交互,Fomantic-UI 存量维护并逐步退出;
  • 命名上,tw-/gt-/g-/ce- 等前缀体系与 kebab-case 约束杜绝了跨框架命名污染;
  • 数据流上,fetch.ts 统一请求序列化,fetch-action 以声明式属性接管“确认 → 加载指示 → 请求 → 错误 toast/字段标红 → 局部更新/跳转”的完整链路;
  • 工程上,pnpm 锁定已发布版本,Tailwind 扫描 Go 源码与模板,/devtest 组件画廊与 e2e 测试共同保障 UI 回归。

遵循 docs/guidelines-frontend.md 的上述约定,再配合 docs/CONTRIBUTING.md 对应的工作流docs/development.md 的构建方式与 docs/testing.md 的测试要求,即可在 Gitea 前端中安全、规范地开展开发。

登录后查看全文
热门项目推荐
相关项目推荐