Radix UI -> Base UI 遷移
你需將 shadcn wrapper、手寫的 radix 組合以及其消費者遷移至 @base-ui/react,並在每一步保持專案可建置。務必精確;切勿猜測映射。當某個屬性或部件未包含在這些參考檔案中時,請先檢查 node_modules/@base-ui/react/**/*.d.ts 再進行轉換,並在報告中記錄差距。
前置檢查 (永遠執行)
- 執行
npx shadcn@latest info --json(或專案的執行器):取得當前基礎、STYLE(例如radix-lyra)、tailwind 版本、別名、已安裝的元件與套件管理器。信任此輸出而非自行推斷。 - 偵測套件管理器(packageManager 欄位 / 鎖定檔:pnpm-lock.yaml, bun.lock, yarn.lock, package-lock.json)並使用它進行每一次安裝。絕不留過時的鎖定檔。
- 要求乾淨的 git 樹;在分支上作業;每個元件一個提交。
- 在觸碰相依套件之前進行基準檢查:執行專案的 typecheck/build,這樣預先存在的失敗就不會歸咎於你。
- 安裝
@base-ui/react與 radix 並存。Radix 套件只會在最後一個元件遷移完成後才移除(兩者可以和平共處)。
策略:先找黃金配對,再使用轉換引擎
- 透過 CLI 取得黃金配對 (首選)。 如果專案是 shadcn 且具有已知樣式 (
radix-<style>),則 shadcn CLI 本身就是黃金配對的執行者:- 首先對每個 ui wrapper 進行分類:將使用者的檔案與其原始範本進行差異比對,使用 components.json 中的 style 確切不變地放入 URL (
https://ui.shadcn.com/r/styles/<style>/<component>.json,files[0].content)。這同時適用於有前綴的樣式 (radix-nova) 以及舊版無前綴的樣式 (new-york, new-york-v4, default),這些都仍然在服務中。 - 完整專案模式:現在就翻轉
components.json中的 styleradix-<style>->base-<style>。漸進式模式:先不要翻轉(專案大部分仍是 radix;翻轉只會在最後一個元件之後進行一次);改為直接透過 URL 擷取 base 變體 (https://ui.shadcn.com/r/styles/base-<style>/<component>.json)。 - 原始 wrapper,完整專案模式:
shadcn add <component> --overwrite會以專案確切的圖示/字型/預設解析來交付 base 變體。絕不要使用--all --overwrite;請逐個元件進行,否則你會淹沒在無關的註冊表版本漂移中。漸進式模式:絕不要使用--overwrite(它會破壞消費者仍在匯入的原始檔案);改為將擷取到的 base 變體內容寫入<component>-base.tsx。 - 客製化 wrapper:擷取 base 變體並將使用者的差異重新套用到其上(他們的客製化必須保留;
--overwrite會破壞它們)。可大規模運作的機械化實作:git merge-file user.tsx radix-golden.tsx base-golden.tsx(三向合併,以 radix golden 作為祖先)可自動解決大部分檔案;對於衝突則使用參考表格手動解決。 - 對每個黃金配對檔案(包含那些合併「乾淨」的)執行強制性的殘留掃描:
grep -n "radix-ui\|@radix-ui\|IconPlaceholder"每個檔案。註冊表有時會重新排列變體之間的函式,這會讓三向合併回報零衝突,但實際上卻留下了舊的 radix 程式碼片段。乾淨的合併並不代表檔案是乾淨的。 這比重建轉換更可靠;只要存在配對就使用它。消費者/應用程式碼沒有 CLI 機制:一律根據consumer-props.md手動遷移。
- 首先對每個 ui wrapper 進行分類:將使用者的檔案與其原始範本進行差異比對,使用 components.json 中的 style 確切不變地放入 URL (
- 舊版樣式 (new-york, new-york-v4, default):僅分類,不重播。 這些樣式沒有對應的 base 版本(不存在 base-new-york),而且重新定位到 base- 變體會重新設計使用者的應用程式樣式。僅使用 radix golden 來偵測客製化,然後在使用者自己的檔案上執行轉換引擎:重新接線基本元件,保留他們確切的類別,套用類別映射重新命名。他們的外觀保持不變。在舊版完整專案遷移結束時,標記(不要修正):樣式名稱對 CLI 來說仍然是 radix,因此未來的
shadcn add將會交付 radix 變體;由使用者決定是否切換樣式或手動新增。 - 轉換引擎 (備用)。 手寫的 radix 程式碼、非 shadcn 專案、未知的樣式:使用
universal-patterns.md(兩種形式的匯入:radix-ui和@radix-ui/react-*;asChild -> render 以及可運作的範例;Portal>Positioner>Popup;positioner FORWARD 規則;部件重新命名)、每個系列的 props 表格 (overlays.md,menus.md,form-controls.md,disclosure.md,display-misc.md)、class-mapping.md用於 data-attribute/CSS-var 重寫,以及wrapper-shapes.md用於確切的目標形狀(tooltip arrow, SubContent 預設值, select 結構)。
模式
漸進式 (預設)。 「遷移 accordion」 = 一個元件,strangler-fig 模式:
- 首先偵測進行中的狀態:是否存在
<component>-base.tsx,消費者分散於舊/新匯入。檔案本身就是狀態;繼續,永遠不要重新開始。 - 如果該元件匯入其他仍在使用 radix 的 ui wrapper(select -> button),停止並建議先從底層開始遷移這些元件。
- 將遷移後的版本寫入
<component>-base.tsx(原始檔案不動;黃金配對內容透過 URL 擷取,或根據上述策略手動轉換);執行 typecheck。一次重新指向一個消費者(匯入 +consumer-props.md中的呼叫端 props);每個都執行 typecheck。當沒有消費者匯入原始檔案時:刪除它,將-base重新命名回原始名稱,復原匯入,最終檢查,提交。當專案中最後一個 radix wrapper 完成最終化時,將components.json翻轉為base-<style>並移除 radix 相依套件。
完整專案 (僅在明確要求時):相同的逐個元件工作,但依照相依性順序(先處理 leaf/共享 wrapper,如 button 和 label)。在 wrapper 之後,根據 consumer-props.md 掃描所有應用程式碼——呼叫端的破壞表面比 asChild 大得多。然後移除 radix 相依套件,安裝,完整建置。
硬性規則
- 絕對不要觸碰非 radix 的函式庫或其 wrapper:cmdk (command), vaul (drawer), sonner, input-otp, react-day-picker (calendar), recharts (chart)。將它們報告為刻意未觸碰。
- 無對應的 Base UI 元件:AspectRatio -> CSS aspect-ratio div;Label -> 原生
<label>;VisuallyHidden ->sr-only;Direction -> Direction Provider (directionprop,不是dir)。Popover Anchor 和 NavigationMenu Indicator 沒有對等物:惰性傳遞 + 標記。 button.tsx遷移至真正的@base-ui/react/button基本元件,絕不是手寫的 useRender wrapper。- 行為差異要被標記,絕不靜態修補(tabs 手動啟用、menu 項目點擊不關閉、nav-menu 50ms 延遲)。目標是符合 shadcn base 註冊表的慣用 Base UI。
- 誠實報告:跳過/復原的檔案要列為已標記,絕不能列為已遷移。預先存在的失敗要命名為預先存在。
驗證與報告
每個檔案執行 typecheck,每批執行 build,最後對比基準執行完整建置。
報告存放在專案根目錄的 .migration/ 目錄下,每個元件一個檔案:.migration/<component>.md(例如 .migration/accordion.md)。
規則:
- 每次執行會為其遷移的每個元件寫入(或完全覆蓋)該檔案。重新執行某個元件會取代其報告;絕不觸碰其他元件的檔案。
- 一個多元件執行(「遷移 alert-dialog 和 dropdown-menu」)會為每個元件寫入一個檔案,每個檔案自包含;共享的消費者掃描備註會重複出現在每個受影響的檔案中。
- 完整專案模式會寫入每個元件的檔案,再加上
.migration/project.md(相依套件交換、應用程式碼掃描摘要、最終建置結果)。 - 沒有索引檔案。遷移狀態是從磁碟推導而來,而非維護:當被問到「還剩下什麼」時,掃描專案的 ui 目錄(來自 shadcn info 的
ui別名,例如 components/ui 或 src/components/ui)中是否還有剩餘的 radix 匯入。在每次執行的摘要結尾加上這個推導出來的數字(「還有 N 個 wrapper 使用 Radix」)。
每個 .migration/<component>.md 使用完全相同的結構(這是公開的文件;報告必須與之相符):
# <component>
<日期, 使用的策略 (golden pair via CLI / merge / engine), 一行判決>
## 已變更
<每個觸碰過的檔案,說明變更了什麼以及原因;對於任何值得注意的部分包含 檔案:行號。確認殘留掃描是乾淨的:
對這個元件的檔案執行 grep -n "radix-ui\|@radix-ui">
## 未觸碰
<看起來相關但刻意未觸碰的檔案,以及原因 (cmdk/vaul/sonner 不是 radix;無關的漂移;等等)>
## 行為變更
<編譯沒問題但行為不同的差異;已標記,絕不修補 (tabs 啟用、menu 點擊關閉、延遲...)。如果沒有則留空>
## 手動驗證
<針對此基本元件系列的簡短手動 QA 檢查清單:對話框的焦點返回、選單/選擇的鍵盤導航 + 類型預覽、工具提示的延遲感受、滑塊的提交事件。具體步驟,約一分鐘的點擊操作>