{% extends "base.html" %} {% from "_parts.html" import code, defs, lesson, prose, stage %} {% from "ui/layout.html" import grid, page_header, stack %} {# 首頁的中文版:fjkit 是什麼、四個步驟做出第一頁、下一步該看哪一頁。 每一句主張都在其他頁示範——接線與 htmx 在「上手」,簽名在「元件」——所以這一頁保持簡短並交棒。 唯一不能交棒的是第一次執行:讀完 01 的人手上就有一個能動的頁面。 行文規則:一句話講一件事,結論放前面。 #} {% set sections = [ {"id": "start", "num": "01", "title": "開始使用"}, {"id": "who", "num": "02", "title": "給誰用"}, {"id": "box", "num": "03", "title": "wheel 裡裝了什麼"}, {"id": "next", "num": "04", "title": "接下來去哪"}, ] %} {# 三條,決定讀者要不要繼續讀下去。每一條的理由在 CHARTER.md 第 2 節。 純字串,不是 Markup:需要塞 的特色表示還沒寫得夠白話,不放首頁。 #} {% set features = [ ("零建置", "沒有 package.json、沒有 node_modules、沒有 watcher。stylesheet 在 fjkit 發佈時編譯,不在你的 app 執行時編譯。"), ("顏色只有一個開關", "每個顏色都是 token。換品牌是一份 stylesheet,狀態色不跟著變,所以綠色仍然代表完成。"), ("htmx 端點同時是 JSON API", "渲染 partial 的路由對 htmx 回 HTML,對其他呼叫者回 response model 的 JSON。"), ] %} {% block content %} {% call stack(8) %} {% set intro %} 給 FastAPI 的頁面、表格、表單、導覽與 htmx 局部更新,以 Jinja macro 寫在路由旁邊。 {% endset %} {{ page_header("給 FastAPI 的快速 UI", intro) }} {# 先講問題,再講特色。 這一頁原本開頭就是三條特色,而特色只對「已經有這個問題」的讀者有意義。兩段, 點名 FastAPI 作者今天實際會走的兩條路,以及各自的代價,讓「零建置」是在回答 某件事,而不是憑空的形容詞。 兩條路都不指名產品。跟具名工具比較的頁面,會因為別人發版而過期。 #} {% call prose() %}

一個需要真正介面的 FastAPI 服務,通常只有兩條路。自己寫 Jinja 再手刻 CSS, 於是你多了一套本來沒打算做的設計系統。改用前端框架,於是你多了一套建置、 一種語言,以及第二個要部署的東西。

fjkit 是第三條路。介面是寫在路由旁邊的 Jinja macro,stylesheet 在 fjkit 發佈時 就編譯好了——所以你的專案不會出現 package.json,改模板也沒有任何 東西要重新 build。

{% endcall %} {# 先給證據,再給說法。一個 UI 套件的首頁如果沒有任何它產出的畫面,就是要讀者 憑信任。 兩個檔案,跟著主題切換。說明文字裡的主張包含深色模式,一張圖只能證明一半。 代價記在 `assets/brand.css` 的 GAP 2。 #} {% set shot_caption %}位於 examples/fjkit-demo 的示範。導覽、深色模式、可篩選表格、會驗證的表單與 htmx 局部更新——而那個 app 裡沒有任何一個 .css 或 .js 檔。{% endset %} {% call stage("這一頁是 kit 畫出來的", shot_caption, padded=false) %} 一個任務看板:左側九個區段的側欄、新增任務表單、八列任務的表格(含狀態與優先度標籤),右側是進度卡片。 同一個任務看板的深色模式。 {% endcall %} {{ defs("主要特色", features) }} {# ============================================================== 01 #} {% set i01 %}安裝一個套件,寫一個 handler 與一份模板,執行 app。{% endset %} {% call lesson("start", "01", "開始使用", i01) %} {% call prose() %}

需要 Python 3.13 以上。

{% endcall %} {{ code("uv add fjkit", "bash", label="1 · 安裝") }} {{ code("from pathlib import Path from fastapi import FastAPI from fjkit import FjkitConfig, mount_fjkit, render from pydantic import BaseModel app = FastAPI() mount_fjkit(app, FjkitConfig(template_dir=Path(__file__).parent / \"templates\")) class Overview(BaseModel): done: int @app.get(\"/\") @render(\"overview.html\") def overview() -> Overview: return Overview(done=18)", label="2 · app/main.py") }} {% call prose() %}

mount_fjkit 供應 stylesheet 與內附的 JavaScript,並建立 Jinja environment。 @render 寫在 @app.get 下方,把回傳 model 的欄位交給模板。

{% endcall %} {{ code("{% extends \"ui/shell.html\" %} {% from \"ui/layout.html\" import page_header, grid %} {% from \"ui/data.html\" import stat %} {% block content %} {{ page_header(\"Overview\", \"How the board is doing\") }} {% call grid(cols=4) %} {{ stat(\"Done\", done, tone=\"success\", icon_name=\"check\") }} {% endcall %} {% endblock %}", "jinja", label="3 · app/templates/overview.html") }} {{ code("uv run fastapi dev app/main.py", "bash", label="4 · 執行") }} {% call prose() %}

開啟 http://127.0.0.1:8000。頁面有 header、深色模式切換與一張統計卡。 模板裡沒有 CSS class,也沒有顏色:macro 接受具名選項,例如 tone="success", 由內附的 stylesheet 負責呈現。

把 uv run fjkit check app/templates 加進 CI。模板使用詞彙表以外的 class 或顏色時,它會失敗。

{% endcall %} {% endcall %} {# ============================================================== 02 #} {% set i02 %}會寫後端、需要一套介面、不想碰前端工具鏈的人。{% endset %} {% call lesson("who", "02", "給誰用", i02) %} {% call prose() %}

適用對象:已經在寫 FastAPI,需要導覽、深色模式、一張可篩選的表格,以及一個會驗證的表單。 若要使用 React、Vue 或 Svelte,請直接用它們:fjkit 在伺服器端渲染,沒有前端元件模型。

{% endcall %} {{ defs("預期成本", [ ("第一頁", "約二十行,如 01 所示,沒有建置步驟"), ("一個後台畫面", "列表、篩選、新增表單與 htmx 更新,不寫 CSS,也不寫 JavaScript"), ]) }} {% endcall %} {# ============================================================== 03 #} {% set i03 %}一個 wheel、兩個執行期依賴,不需要自行編譯任何東西。{% endset %} {% call lesson("box", "03", "wheel 裡裝了什麼", i03) %} {# 三個數字來自 `css`,由 `build.py` 量測本站連到的那份 stylesheet。 手寫過一次,而且是錯的。 #} {% set css_line %}編譯好的 stylesheet,每個風格包一份。{{ css.raw_kb }} KB 原始、{{ css.gzip_kb }} KB gzip;超過 {{ css.budget_kb }} KB 時 build 失敗。一頁只載一份。{% endset %} {% call grid(cols=2, gap=4) %} {{ defs("套件裡帶的", [ ("templates/ui/", "macro:版面、按鈕、資料、表單、表格、分頁、導覽、側欄、回饋、圖示與文件外殼"), ("static/dist/fjkit-.css", css_line), ("static/vendor/", "htmx 與 Basecoat 的 JavaScript,版本釘死,直接從套件裡供應"), ("1,767 個圖示", "Lucide,按名字內嵌。沒有 icon font、沒有 sprite 請求、執行期沒有 JS。"), ]) }} {{ defs("你會用到的全部介面", [ ("FjkitConfig、mount_fjkit", "接線,每支 app 一次"), ("@render", "指名模板;handler 只回資料"), ]) }} {% endcall %} {% call prose() %}

執行期依賴:FastAPI 與 Jinja2。

{% endcall %} {% endcall %} {# ============================================================== 04 #} {% set i04 %}依你要做的事選頁面。{% endset %} {% call lesson("next", "04", "接下來去哪", i04) %} {% set n_learn %}上手——app 怎麼接線、htmx 局部更新怎麼運作、表單錯誤、訊息、換品牌與 fjkit check。先讀 01–05。{% endset %} {% set n_components %}元件——每支 macro 的實際渲染,附上產生它的呼叫{% endset %} {% set n_cheatsheet %}速查表——所有簽名、外殼 block、模板全域變數與 htmx 屬性,集中在一頁{% endset %} {% set n_plugins %}外掛——登入、flash 訊息、圖表、API 主控台與 admin,以及如何自己寫外掛{% endset %} {% set n_arch %}架構——五個已定案的決定,用圖說明:套件邊界、eject、build 發生的時機、一份 partial 兩個入口,以及守門迴圈{% endset %} {% set n_why %}為什麼用——其他做法的代價,以及這個做法換來什麼又放棄什麼{% endset %} {{ defs("這個站上", [ ("決定到底要不要用它", n_why), ("先看清楚它的形狀再決定要不要用", n_arch), ("做出第一個真正的畫面", n_learn), ("找一個元件", n_components), ("查一個參數", n_cheatsheet), ("加上登入、圖表或 admin", n_plugins), ]) }} {% endcall %} {% endcall %} {% endblock %}