Yosegi — 從主機自己的元件建構螢幕畫面
版本:2026-08-13。 代理工具集可能載入過期的技能副本而不另行通知。如果此日期晚於專案儲存庫中
skills/yosegi/SKILL.md的日期,則表示您正在閱讀過期副本 — 請先重新安裝再繼續。
Yosegi 將主機專案中的元件轉化為可用於建構螢幕畫面的事實,並在實作任何內容之前,將螢幕畫面輸出為 Story(CSF)以供審查。它本身既沒有渲染環境,也沒有自己的圖形使用者介面;您在主機的 Storybook 中檢查畫面外觀。在沒有 Storybook 的主機上,交付成果會變成純 React 元件檔案(--target component)— 下方的步驟 1 分支說明了程序如何變化。
本文檔即是操作程序,而您是執行者。CLI(以及等效的 MCP 工具)是程序會調用的工具。
何時使用
- 從現有的設計系統組合螢幕提案或 UI 模型,並以可審查的形式呈現。
- 在實作之前,先在 Storybook 中呈現新螢幕的初稿。
- 在撰寫任何 UI 之前,查詢主機有哪些元件以及它們實際接受的屬性。
- 將已核准的模型 Story 轉錄為真實頁面。執行步驟 1 和 2,然後跳到步驟 5。
不適用於:一次性元件的修改。
價值所在
登錄檔的任務範圍很窄:它是元件屬性的真實來源 — 真實的屬性名稱、真實的列舉選項、哪些是必要的、哪些是槽位(slot)而非子元件(children)、函數屬性的呼叫簽章。主機的元件並非它們被分支出來的函式庫。它們的變體列舉已被重新命名、它們的尺寸比例在兄弟元件之間不同、圖示屬性接受元件參考而非元素、ReactNode 屬性是具名槽位而非子元件,以及三個不同的元件共用一個匯出名稱。這些都無法從對 React 或上游函式庫的熟悉程度推導出來,而每一個都是如果您寫入您預期的屬性而不是登錄檔記載存在的屬性時,您將會交付的缺陷。這就是步驟 1 和 2 的 inspect 呼叫的目的,而跳過它們就是一個看似合理的模型如何交付缺陷的方式。
登錄檔不提供螢幕的骨架、組合慣用語或佈局 — 驅動 DataGrid 的網格函式庫鉤子呼叫、主機特定的 meta 如 { stickyHeader: true }、元件預期包裹它的包裝器。這些來自主機自己的 Story,以及在具有頁面/路由產生器的主機上,來自其範例模板(步驟 2)。主機的模板和 Story 涵蓋範圍越豐富,登錄檔在您實際建構螢幕所需內容中所佔的份額就越小 — 這不是缺陷。登錄檔和主機自己的範例回答了不同的問題,並且兩者對於它們所回答的問題都是承載性的。
參考資料
僅在下方步驟指示您查閱時才開啟這些檔案,不要預先開啟。在 3a 路徑(直接撰寫 Story)上,可能永遠不需要:registry status 和 component inspect 自身的輸出以及本文檔的主體通常已足夠自我描述來建構整個螢幕。不過,一旦您確實需要其中一個,請依據它來運作,而不是僅憑記憶中的本文檔 — 其詳細資訊在此不會重複。
| 檔案 | 何時閱讀 |
|---|---|
references/registry.md | 當 inspect 的輸出中有您無法解釋的內容時 — 不透明屬性的 shape: / signature: 行、未記錄的屬性、看起來錯誤的匯入說明符 |
references/cli.md | 當命令或標誌未在上面涵蓋、CLI 無法執行,或您需要 MCP 等效命令時 |
references/screen-json.md | 僅當您採用螢幕 JSON 路徑(3b)時。格式以及它無法表達的內容 |
references/errors.md | 在任何命令印出錯誤或警告的時刻。每個錯誤碼及其修正方法 |
references/implementation.md | 用於步驟 3 中的元模板,以及在步驟 5 之前 |
步驟 1. 建構登錄檔
Yosegi 需要主機提供一個可讀取的 tsconfig.json(屬性來自 TypeScript 型別,僅此即足夠)。Storybook 是 Story 的存放位置以及您確認其渲染效果的地方 — 但它並非必要條件。
如果您無法偵測到 Storybook — 沒有
.storybook/(或等效)配置目錄、主機package.json中沒有storybook腳本、沒有index.json可提供給--index— 則交付成果是純 React 元件檔案而非 Story (screen generate --target component,請參閱references/cli.md)。不要自行決定輸出位置或審查方法:在建構任何東西之前,先詢問使用者 (a) 元件檔案應該放在哪裡,以及 (b) 他們希望如何確認結果(例如在主機應用程式的臨時路由上渲染它)。Yosegi 不規定審查介面,而猜測任何一個答案都違反了與猜測屬性相同的規則。有了這些答案,下面的程序保持不變,但有三個差異:registry build執行時不加--index/--storybook-url(您會失去策展功能,但其他不受影響);在 3a 路徑上,您在使用者命名的位置撰寫元件檔案,而在 3b 路徑上,您傳遞--target component --out <那個位置>.tsx;步驟 4 的 Storybook 檢查將被主機的型別檢查以及使用者命名的確認方法取代。
- 您能執行 CLI 嗎?
npx yosegi(或pnpm yosegi、yarn yosegi、bunx yosegi)不帶任何參數會印出每個命令,前提是@yosegi/yosegi已經是主機的依賴項 — 如果不是,請先安裝它(請參閱references/cli.md)。需要 Node.js 20 或更新版本,以及 React + TypeScript 主機。如果命令反而失敗,請參閱references/cli.md中的「如果 CLI 無法執行」;最有用的做法是,如果已有來自先前會話的登錄檔,請讀取<data-dir>/registry.json的builtWithCliPath。 - 立即指定一個
--data-dir並將同一個傳遞給每個命令。使用主機套件根目錄下的.yosegi,這也是預設值;下面的範例仍然會寫出它,因為預設值會跟隨您的當前工作目錄,而從其他目錄執行的命令將找不到您建構的內容。 - 是否已經有登錄檔,並且是最新的? 首先執行
yosegi registry status --data-dir .yosegi— 它會根據自己記錄的輸入重新計算登錄檔的版本,並報告source: current或source: stale以及確切的重新建構命令,而無需您自行查看component list的標題。「找不到登錄檔」表示這是首次執行。其 Storybook 索引部分盡力而為,可以在來源判定仍然成立時報告索引無法訪問 — 請將該部分解讀為「無法驗證」,而不是「過期」或「最新」,並且不要讓無法訪問的開發伺服器阻止您信任current的來源判定。每當status報告為 stale、每當有任何東西印出REGISTRY_VERSION_MISMATCH、或每當status報告source: unknown(因為登錄檔早於記錄輸入的功能,顯示built: not recorded)時,都應重新建構 — 那裡沒有可重新計算的內容,因此年齡是您唯一擁有的信號。每當命令警告登錄檔是由與您正在執行的不同 Yosegi 版本建構時,也應重新建構:較舊的 Yosegi 無法輸出此版本可以處理的欄位(例如函數屬性的呼叫簽章),並且沒有其他信號會顯示該差距 — 版本字串作為內容雜湊會保持相同,因此從未更改的主機重新建構的登錄檔會保留同一個版本。登錄檔是一個快照,過期的登錄檔比沒有更糟。
yosegi registry build \
--source "app/components/**/*.tsx" \
--tsconfig <host>/tsconfig.json \
--index http://localhost:6006/index.json \
--storybook-url http://localhost:6006 \
--report tmp/registry-report.json \
--data-dir .yosegi
上面的 6006 是 Storybook 自己的預設埠,僅因為這是首次建構且尚未有其他資訊告知您而使用 — 它不一定是主機的連接埠。在執行此命令之前,請檢查主機的 package.json 中的 Storybook 腳本(或詢問)以取得其實際運行的連接埠。首次建構之後的每次重新建構都有比猜測更好的來源:component list 的標題會印出一行 rebuild:,其中包含登錄檔上次建構時使用的確切 --storybook-url(請參閱 references/cli.md)。
標誌和預設值在 references/cli.md 中。--index 是唯一有前提條件的部分:開發伺服器 URL 意味著主機的 Storybook 必須已經在執行中。沒有它,您會失去來自 Story 的分類和 recommended 標記,其他不受影響。
閱讀它印出的統計資料:
files: 0→ glob 模式沒有匹配任何內容。命令仍然成功並寫入一個僅包含三個合成基本元件的登錄檔,因此不要將「Wrote 3 components」解讀為成功。componentCandidates: 0搭配正數的files,或withNodeSlots: 0搭配高數值的anyShapedProps→ 建構雖然成功但品質下降。前者表示 glob 涵蓋了沒有 React 元件的檔案;後者表示@types/react未解析,導致每個ReactNode屬性都扁平化為json且未偵測到任何槽位。兩種情況下都會有Warning:行指明修正方法 — 解決它並在繼續之前重新建構。propsUnreadable相對於extractedComponents數值較高 → tsconfig 可能錯誤。立即解決它(請參閱references/cli.md、registry metadata);處於該狀態的元件稍後會拒絕完全真實的屬性。
步驟 2. 查閱您打算使用的每個元件
如果頁面/路由產生器的範例模板是您正在建構的螢幕最接近的匹配項,請先閱讀它,或與下面的命令一起閱讀 — 它決定了骨架和組合慣用語,而登錄檔隨後確認模板留空的確切屬性。
yosegi component list --category app/components/ui --data-dir .yosegi
yosegi component list --query card --data-dir .yosegi
yosegi component inspect "app/components/ui/button#Button" --data-dir .yosegi
登錄檔可能多達數百個條目,因此使用 --category / --query 縮小範圍,從摘要中了解概況,然後對您將要撰寫的每個元件執行 inspect。inspect 是權威性的,而您的框架知識則不是。 閱讀 references/registry.md 以了解如何解讀它提供的內容 — 列舉選項、不透明屬性下的 shape: 和 signature: 行、主機實際撰寫的匯入說明符,以及未記錄屬性的含義。
優先選擇標記為 recommended 的元件;它們有自己的 Story。
然後收集主機的慣例。Story 會放置在主機的儲存庫中,並作為主機的程式碼進行審查;跳過此步驟會產生一個能運作但仍會被退回的 UI。
- 尋找資料合約(如果已存在) — OpenAPI/JSON Schema 定義、GraphQL 架構、為 API 回應手寫的 TypeScript 型別,無論主機資料層使用何種形式。在發明模擬資料之前先閱讀它。它比元件屬性更能決定更多事項:哪些欄位是必要的相對於可為 null 的,以及對於特定欄位 null 意味著什麼(null 的 limit 可能表示「無限制」,而不是「未知」)、哪些欄位是互斥的,以及(同樣有用)什麼是不存在的(合約中沒有排序或搜尋欄位意味著螢幕也不應發明一個)。這就是將模型從外觀提案轉變為反映真實資料形狀的東西。它的位置因主機而異(
openapi/schemas/、.graphql檔案、產生的types.ts);為這個領域找到一個並遵循它,而不是做出看似合理的猜測。 - 在主機的路由/頁面目錄(
app/routes/或任何提供頁面的位置)中搜尋同一領域中現有的螢幕。在成熟的產品中通常會有一個,而找到它可以完全重新定義任務 — 例如,現有的bookings.calendar._index路由可能將「製作一個預訂螢幕」轉變為「針對現有的預訂流程提出一個跨領域的清單」。這決定了您被要求製作的模型是否甚至是要建構的正確事物,因此請在下方的檢查點之前執行此操作,並讓它形塑您在那裡提出的問題。 - 在建構任何東西之前,執行
git status並查看輸出目錄。 較早的、未提交的相同螢幕嘗試可能已經位於工作樹中 — 在內容重置和重新發出請求後很常見。與其協調,而不是在旁邊建立重複項。要重做一個已存在的檔案 — 主機產生器在目標路徑已存在時會拒絕,或者需要取代較早的嘗試 — 將其移開(重新命名)而不是刪除它;刪除可能對您不可用,而重新命名無論如何都能清除路徑。 - 檢查主機是否具有用於新頁面或螢幕的產生器 — plop/hygen 產生器、
bun add-route風格的腳本,通常記錄在主機的AGENTS.md或package.json腳本中。在具有此類產生器的主機上,從頭開始手寫一個新的螢幕檔案是錯誤的:產生器會複製經過批准的模板並強制執行檔案和命名慣例,因此請執行它而不是自行建立檔案。這也決定了步驟 3 中內聯樹狀 Story 與頁面模組加上薄包裝器 Story 之間的選擇 — 具有此類產生器的主機幾乎總是想要後者。 - 從儲存庫根目錄到輸出目錄的每個層級讀取
AGENTS.md/CLAUDE.md,並遵循它們指向的任何文件。 - 在輸出位置附近閱讀一兩個現有的 Story。組合多個元件的複合範例是值得模仿的,以獲得頁面骨架、間距和元件選擇。對於組合慣用語 — 如何實際組裝元件,而不僅僅是它接受哪些屬性:驅動 DataGrid 的網格函式庫鉤子呼叫、主機特定的
meta如{ stickyHeader: true }、元件預期包裹它的包裝器 — 現有的 Story 是真實來源,而登錄檔則不是。 登錄檔
…
[基於給定內容截斷]