前端開發指南
背景
Gitea 使用 Fomantic-UI(基於 jQuery)和 Vue3 作為前端框架。
HTML 頁面由 Go HTML Template 渲染。
源文件可以在以下目錄中找到:
- CSS 樣式:
web_src/css/
- JavaScript 文件:
web_src/js/
- Vue 組件:
web_src/js/components/
- Go HTML 模板:
templates/
一般指南
我們推薦 Google HTML/CSS Style Guide 和 Google JavaScript Style Guide
Gitea 特定指南
- 每個功能(Fomantic-UI/jQuery 模塊)應該放在單獨的文件/目錄中。
- HTML id 和 class 應該使用 kebab-case,最好包含 2-3 個與功能相關的關鍵詞。
- 在 JavaScript 中使用的 HTML id 和 class 應該在整個項目中是唯一的,並且應該包含 2-3 個與功能相關的關鍵詞。我們建議對僅在 JavaScript 中使用的 class 使用
js-
前綴。 - 不應覆蓋框架提供的 class 的 CSS 樣式。始終使用包含 2-3 個與功能相關的關鍵詞的新 class 名稱來覆蓋框架樣式。Gitea 的
helpers.less
中的輔助 CSS class 可能會有所幫助。 - 後端可以使用
ctx.PageData["myModuleData"] = map[]{}
將複雜數據傳遞給前端,但不要將整個模型暴露給前端,以避免洩露敏感數據。 - 簡單頁面和與 SEO 相關的頁面使用 Go HTML Template 渲染生成靜態 Fomantic-UI HTML 輸出。複雜頁面可以使用 Vue3。
- 明確變量類型,優先使用
elem.disabled = true
而不是elem.setAttribute('disabled', 'anything')
,優先使用$el.prop('checked', var === 'yes')
而不是$el.prop('checked', var)
。 - 使用語義化元素,優先使用
<button class="ui button">
而不是<div class="ui button">
。 - 避免在 CSS 中不必要的
!important
,如果無法避免,請添加註釋解釋為什麼是必要的。 - 避免在一個事件監聽器中混合不同的事件,優先為每個事件使用單獨的事件監聽器。
- 自定義事件名稱建議使用
ce-
前綴。 - 優先使用 Tailwind CSS,通過
tw-
前綴可用,例如tw-relative
。Gitea 的輔助 CSS class 使用gt-
前綴(gt-ellipsis
),而 Gitea 自己的私有框架級 CSS class 使用g-
前綴(g-modal-confirm
)。 - 盡可能避免內聯腳本和樣式,建議將 JS 代碼放入 JS 文件並使用 CSS class。如果內聯腳本和樣式是不可避免的,請解釋為什麼無法避免。
無障礙 / 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 class。 我們使用 htmx 進行簡單的交互。你可以在這個 PR 中看到一個使用 htmx 進行簡單交互的示例。如果你需要更高級的反應性,請使用其他框架(Vue/原生 JS)。 雖然不建議混合使用不同的框架, 但如果混合是必要的並且代碼設計良好且可維護,應該也是可行的。
Typescript
Gitea 正在遷移到類型安全的 Typescript。以下是一些關於 Typescript 在代碼庫中的具體指南:
使用類型別名而不是接口
優先使用類型別名,因為它們可以表示任何類型,並且通常比接口更靈活。
使用單獨的類型導入
我們使用 verbatimModuleSyntax
,因此來自同一文件的類型和非類型導入必須分成兩個 import type
語句。這使得 typescript 編譯器在編譯過程中可以完全消除類型導入語句。
使用 @ts-expect-error
而不是 @ts-ignore
應避免使用這兩個註釋,但如果必須使用,請使用 @ts-expect-error
,因為在問題修復後它不會留下無效的語句。
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
上下文中調用 async
函數,
建議使用 const _promise = asyncFoo()
來告訴讀者
這是故意這樣做的,我們想調用 async
函數並忽略 Promise。
一些 lint 規則和 IDE 也會在未處理返回的 Promise 時發出警告。
獲取數據
要獲取數據,請使用 modules/fetch.js
中的包裝函數 GET
、POST
等。
它們接受一個 data
選項作為內容,會自動設置 CSRF 令牌並返回一個 Response 的 Promise。
HTML 屬性和 dataset
禁止使用 dataset
,其駝峰命名行為使得很難 grep 屬性。
然而,仍然存在一些特殊情況,所以目前的指南是:
-
對於遺留代碼:
$.data()
應重構為$.attr()
。$.data()
可以在罕見情況下用於將一些非字符串數據綁定到元素,但這是高度不建議的。
-
對於新代碼:
- 不應使用
node.dataset
,應使用node.getAttribute
。 - 永遠不要將任何用戶數據綁定到 DOM 節點,應使用合適的設計模式來描述節點和數據之間的關係。
- 不應使用
顯示/隱藏元素
- Vue 組件建議使用
v-if
和v-show
來顯示/隱藏元素。 - Go 模板代碼應使用
.tw-hidden
和showElem()/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 方法中。當 Gitea 在開發模式下運行時(RUN_MODE=dev
),可以在 http(s)://your-gitea-url:port/devtest
下訪問一些標準化的 UI 示例頁面。