跳至主要内容
版本:1.22.3

前端开发指南

背景

Gitea 使用 Fomantic-UI(基于 jQuery)和 Vue3 用于其前端。

HTML 页面由 Go HTML 模板 渲染。

源文件可以在以下目录中找到

  • CSS 样式: web_src/css/
  • JavaScript 文件: web_src/js/
  • Vue 组件: web_src/js/components/
  • Go HTML 模板: templates/

一般指南

我们推荐 Google HTML/CSS 样式指南Google JavaScript 样式指南

Gitea 特定指南

  1. 每个功能(Fomantic-UI/jQuery 模块)应该放在单独的文件/目录中。
  2. HTML ID 和类名应该使用 kebab-case,最好包含 2-3 个与功能相关的关键字。
  3. JavaScript 中使用的 HTML ID 和类名应该在整个项目中是唯一的,并且应该包含 2-3 个与功能相关的关键字。我们建议为仅在 JavaScript 中使用的类使用 js- 前缀。
  4. 框架提供的类的 CSS 样式不应该被覆盖。始终使用 2-3 个与功能相关的关键字的新类名来覆盖框架样式。helpers.less 中的 Gitea 辅助 CSS 类可能会有所帮助。
  5. 后端可以使用 ctx.PageData["myModuleData"] = map[]{} 将复杂数据传递给前端,但不要将整个模型暴露给前端,以避免泄露敏感数据。
  6. 简单的页面和 SEO 相关的页面使用 Go HTML 模板渲染以生成静态的 Fomantic-UI HTML 输出。复杂的页面可以使用 Vue3。
  7. 澄清变量类型,优先使用 elem.disabled = true 而不是 elem.setAttribute('disabled', 'anything'),优先使用 $el.prop('checked', var === 'yes') 而不是 $el.prop('checked', var)
  8. 使用语义元素,优先使用 <button class="ui button"> 而不是 <div class="ui button">
  9. 避免在 CSS 中不必要的 !important,如果无法避免,请添加注释解释原因。
  10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用单独的事件监听器。
  11. 自定义事件名称建议使用 ce- 前缀。
  12. 优先使用 Tailwind CSS,它可以通过 tw- 前缀使用,例如 tw-relative。Gitea 的辅助 CSS 类使用 gt- 前缀 (gt-word-break),而 Gitea 自身的私有框架级 CSS 类使用 g- 前缀 (g-modal-confirm)。
  13. 尽可能避免内联脚本和样式,建议将 JS 代码放入 JS 文件中并使用 CSS 类。如果内联脚本和样式不可避免,请解释为什么无法避免。

可访问性/ARIA

在历史上,Gitea 严重依赖 Fomantic UI,它不是一个无障碍友好的框架。Gitea 使用了一些补丁来使 Fomantic UI 更易于访问(参见 aria.md 和相关的 JS 文件),但仍然存在许多问题,需要大量的工作和时间才能解决。

框架使用

不鼓励混合使用不同的框架,这会使代码难以维护。JavaScript 模块应该遵循一个主要的框架并遵循框架的最佳实践。

推荐的实现

  • Vue + 原生 JS
  • Fomantic-UI (jQuery)
  • htmx(用于其他静态组件的页面部分重新加载)
  • 原生 JS

不鼓励的实现

  • Vue + Fomantic-UI (jQuery)
  • jQuery + 原生 JS
  • htmx + 任何其他需要大量 JS 代码或不必要功能(如 htmx 脚本 (hx-on))的框架

为了使 UI 保持一致,Vue 组件可以使用 Fomantic-UI CSS 类。我们使用 htmx 来进行简单的交互。您可以在这个 PR 中看到 htmx 应该用于简单交互的示例。如果您需要更高级的反应性,请不要使用 htmx,请使用其他框架 (Vue/原生 JS)。虽然不鼓励混合使用不同的框架,但如果混合是必要的,并且代码设计良好且可维护,它也应该可以工作。

async 函数

仅当且仅当函数内部存在 await 调用或 Promise 返回时,才将函数标记为 async

不建议使用 async 事件监听器,这可能会导致问题。原因是在 await 之后执行的代码是在事件分发之外执行的。参考:https://github.com/github/eslint-plugin-github/blob/main/docs/rules/async-preventdefault.md

如果事件监听器必须是 async,则 e.preventDefault() 应该在任何 await 之前,建议将其放在函数的开头。

如果我们想在非异步上下文中调用 async 函数,建议使用 const _promise = asyncFoo() 来告诉读者这是有意的,我们想调用异步函数并忽略 Promise。一些 lint 规则和 IDE 也会在未处理返回的 Promise 时发出警告。

获取数据

要获取数据,请使用 modules/fetch.js 中的包装函数 GETPOST 等。它们接受用于内容的 data 选项,会自动设置 CSRF 令牌并返回 Response 的 Promise。

HTML 属性和 dataset

禁止使用 dataset,它的驼峰式命名规则使得难以使用 grep 搜索属性。但是,仍然存在一些特殊情况,因此当前指南是

  • 对于遗留代码

    • $.data() 应该重构为 $.attr()
    • $.data() 可以用于在极少数情况下将一些非字符串数据绑定到元素,但强烈不鼓励这样做。
  • 对于新代码

    • 不应使用 node.dataset,而应使用 node.getAttribute
    • 永远不要将任何用户数据绑定到 DOM 节点,请使用合适的模式来描述节点和数据之间的关系。

显示/隐藏元素

  • 建议 Vue 组件使用 v-ifv-show 来显示/隐藏元素。
  • Go 模板代码应该使用 .tw-hiddenshowElem()/hideElem()/toggleElem(),有关更多详细信息,请参见 .tw-hidden 的注释。

Go HTML 模板中的样式和属性

建议使用

<div class="gt-name1 gt-name2 {{if .IsFoo}}gt-foo{{end}}" {{if .IsFoo}}data-foo{{end}}></div>

而不是

<div class="gt-name1 gt-name2{{if .IsFoo}} gt-foo{{end}}"{{if .IsFoo}} data-foo{{end}}></div>

以使代码更易读。

遗留代码

在此文档编写之前,已经存在大量遗留代码。建议将遗留代码重构以遵循指南。

Vue3 和 JSX

Gitea 现在正在使用 Vue3。我们决定不引入 JSX 以保持 HTML 和 JavaScript 代码分离。

UI 示例

Gitea 使用了一些自建的 UI 元素并自定义了其他元素,以更好地将它们整合到一般的 UI 方法中。在开发模式 (RUN_MODE=dev) 下运行 Gitea 时,可以在 http(s)://your-gitea-url:port/devtest 下找到包含一些标准化 UI 示例的页面。