{% extends "base.html" %} {% from "_architecture.html" import boundary, render_path %} {% from "_parts.html" import code, defs, lesson, prose, stage %} {% from "ui/layout.html" import grid, page_header, stack %} {# 從外面看,這個 kit 長什麼樣子——五張圖。 這五張圖在這一頁之前就存在了,是一份只有中文的 Markdown 裡的 mermaid 區塊,而且那份 檔案放在 `docs/`,也就是建置輸出目錄:它以純文字被發佈、從未被渲染、也沒有任何導覽 連到它。五張沒有人看得到的圖。現在它們住在這裡,那份檔案已經刪掉:一個家、兩種語言、 而且在詞彙表之內。 每一節對應 `CHARTER.md` 第 3 節的一個決定。憲章寫的是「決定了什麼、為什麼」,這一頁 畫的是「那個決定長什麼樣子」。兩邊不互相複述,而且每節都標出自己是哪一條。 行文規則:一句話講一件事,結論放前面。 #} {% set sections = [ {"id": "boundary", "num": "01", "title": "套件邊界"}, {"id": "loader", "num": "02", "title": "模板解析"}, {"id": "timing", "num": "03", "title": "build 發生的時機"}, {"id": "render", "num": "04", "title": "渲染路徑"}, {"id": "gate", "num": "05", "title": "守門迴圈"}, ] %} {% block content %} {% call stack(8) %} {% set intro %} 五張圖,對應五個已經定案的架構決定。決定了什麼、為什麼,在 CHARTER.md 第 3 節; 這一頁畫的是那些決定長什麼樣子。 {% endset %} {{ page_header("fjkit 是怎麼組起來的", intro) }} {# ============================================================== 01 #} {% set a01 %}你的 app 只在四個地方碰到 kit,其餘都封在套件裡。{% endset %} {% call lesson("boundary", "01", "套件邊界", a01, tag="A2") %} {% set fig = { "alt": "你的 app 裡四個方塊——routers、page 模板、partial 與 brand.css——各有一條箭頭指向它用到的 fjkit 方塊。", "app": "你的 APP", "routers": "路由、Depends、選模板", "page": "只呼叫 macro,不寫 class", "partial": "htmx 片段", "brand": "只有 --primary 那幾行", "override": "覆寫 token", "shell": "head、主題、資產連結", "vocab": "封閉詞彙表", "css": "八個風格包,發佈時 build 好", } %} {{ boundary(fig) }} {% call prose() %}
四條箭頭都是單向的。你的 app 依賴 fjkit;kit 不會反過來引用你的 app, 也不知道你的資料長什麼形狀。
brand.css 就是全部的設定介面,內容是純 CSS 變數。沒有任何步驟會編譯它。
遮蔽是功能,不是意外:ChoiceLoader 把你的目錄排在套件前面。
代價是 eject 出去的檔案不再收到上游修正,所以文件不主推它。
fjkit eject 只複製你指名的 macro,其餘重新匯出,所以你接手的檔案很小
——以 badge 為例,49 行對上原本的 289 行。
以本站載入的那個風格包實測:{{ css.raw_kb }} KB 原始、{{ css.gzip_kb }} KB gzip, 預算是 build 會強制執行的 {{ css.budget_kb }} KB。原始大小裡約 217 KB 是 Basecoat 自己的元件層,無論 fjkit 在它上面寫了一個 macro 還是四十個,都整份出貨。
{% endcall %} {% endcall %} {# ============================================================== 04 #} {% set a04 %}完整頁面與 htmx swap 走不同的路,落在同一份模板上。{% endset %} {% call lesson("render", "04", "渲染路徑:一份 partial,兩個入口", a04, tag="A6") %} {% set fig = { "alt": "一個 GET 與一個 POST 都走到 tasks/_board.html,然後一條路以完整文件離開,另一條以片段離開。", "only": "唯一的定義", "only_2": "被 include,也被回傳", "document": "一份完整文件", "document_2": "page.html,穿過 shell", "fragment": "一個片段", "fragment_2": "swap 進 #board", } %} {{ render_path(fig) }} {% call prose() %}
_board.html 有兩條進、兩條出,中間只有一份定義。第一次繪製看到的東西,
與 swap 換上去的東西,因此渲染自同一份原始碼,不可能漂移。
保證來自 partial=。同一條路由對導覽回整頁、對 htmx 請求回片段,
所以不存在第二條路由要跟著維護。
這條回饋邊是 kit 唯一的擴張機制,而示範 app 是它的測試案例。0.1 完成時實測: 同樣的畫面在 fjkit 之前寫出來有 258 個違規;用 kit 重寫之後是 0。
這個檢查也擋「目前碰巧存在的 utility」。app 若因為 shell 剛好吐出
gap-4 就拿來用,哪天 shell 不吐了,它就會無聲壞掉。