Radix UI -> Base UI 마이그레이션
shadcn 래퍼, 수제 Radix 구성, 그리고 그 소비자들을 @base-ui/react로 마이그레이션하며, 모든 단계에서 프로젝트가 빌드 가능한 상태를 유지합니다.
정확하게 작업하며 매핑을 추측하지 마세요. 참조 파일에 없는 prop 또는 part는 변환 전에 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) 및 모든 설치에 IT를 사용하세요. 오래된 잠금 파일을 남겨두지 마세요.
- 깨끗한 git 트리 필요; 브랜치에서 작업; 컴포넌트당 하나의 커밋.
- 의존성을 건드리기 전에 기준 검사: 프로젝트의 타입체크/빌드를 실행하여 사전 존재하는 실패가 절대 귀속되지 않도록 하세요.
@base-ui/react를 radix와 함께 설치하세요. Radix 패키지는 마지막 컴포넌트가 마이그레이션된 후에만 제거됩니다(둘 다 잘 공존합니다).
전략: 골든 페어 우선, 변환 엔진 차선
- CLI를 통한 골든 페어 (선호). 프로젝트가 알려진 스타일(
radix-<style>)의 shadcn인 경우, shadcn CLI 자체가 골든 페어 실행기입니다:- 각 UI 래퍼를 먼저 분류: 사용자 파일을 스톡 원본과 비교(diff),
components.json스타일을 URL에 그대로 사용(https://ui.shadcn.com/r/styles/<style>/<component>.json, files[0].content). 이것은 접두사가 있는 스타일(radix-nova)과 접두사가 없는 레거시(new-york, new-york-v4, default) 모두에서 작동하며, 이들은 모두 여전히 제공됩니다. - 전체 프로젝트 모드:
components.json스타일을radix-<style>->base-<style>로 지금 뒤집습니다. 점진적 모드: 아직 뒤집지 마세요(프로젝트는 여전히 대부분 radix입니다; 뒤집기는 마지막 컴포넌트 후에 한 번 발생합니다); 대신 URL로 직접 base 변형을 가져오세요(https://ui.shadcn.com/r/styles/base-<style>/<component>.json). - 손대지 않은(pristine) 래퍼, 전체 프로젝트 모드:
shadcn add <component> --overwrite는 프로젝트의 정확한 아이콘/폰트/프리셋 해석으로 base 변형을 제공합니다. 절대--all --overwrite벌크 작업을 하지 마세요; 컴포넌트별로 진행하세요, 그렇지 않으면 관련 없는 레지스트리 버전 변경에 빠집니다. 점진적 모드: 절대--overwrite를 사용하지 마세요(소비자가 여전히 가져오는 원본을 파괴합니다); 대신 가져온 base 변형 내용을<component>-base.tsx에 작성하세요. - 커스터마이즈된 래퍼: base 변형을 가져와서 그 위에 사용자의 diff를 재생합니다(커스터마이징이 유지되어야 함;
--overwrite는 이를 파괴합니다). 대규모로 작동하는 기계적 구현:git merge-file user.tsx radix-golden.tsx base-golden.tsx(3-way 병합, radix 골든을 공통 조상으로)는 대부분의 파일을 자동으로 해결합니다; 충돌은 참조 테이블을 사용하여 수동으로 해결합니다. - 모든 골든 페어 파일에 대해 필수적인 잔여물 검사, "깨끗하게" 병합된 파일 포함: 파일당
grep -n "radix-ui\|@radix-ui\|IconPlaceholder". 레지스트리는 때때로 변형 간에 함수를 재정렬하여, 3-way 병합이 제로 충돌을 보고하면서 오래된 radix 덩어리를 그대로 남겨둘 수 있습니다. 깨끗한 병합이 깨끗한 파일의 증거는 아닙니다. 이는 변환을 재구성하는 것보다 더 안정적입니다; 쌍이 존재할 때마다 사용하세요. 소비자/앱 코드에는 CLI 메커니즘이 없습니다: 항상consumer-props.md에 대해 수동으로 마이그레이션하세요.
- 각 UI 래퍼를 먼저 분류: 사용자 파일을 스톡 원본과 비교(diff),
- 레거시 스타일 (new-york, new-york-v4, default): 분류만, 재생 없음. 이들은 base 대응물이 없으며(base-new-york은 없음), base- 변형으로 재타겟팅하면 사용자 앱의 스타일이 변경됩니다. 커스터마이징을 감지하는 데에만 radix 골든을 사용한 다음, 사용자 자신의 파일에 변환 엔진을 실행합니다: 프리미티브 재연결, 정확한 클래스 유지, 클래스 매핑 이름 변경 적용. 그들의 모양은 그들의 것입니다. 레거시 전체 프로젝트 마이그레이션 끝에, 플래그 표시(수정하지 마세요): 스타일 이름이 여전히 CLI에 radix로 읽히므로, 미래의
shadcn add는 radix 변형을 제공할 것입니다; 사용자는 스타일을 전환할지 아니면 수동으로 추가할지 결정합니다. - 변환 엔진 (폴백). 수제 Radix 코드, 비-shadcn 프로젝트, 알 수 없는 스타일:
universal-patterns.md(두 형태 모두의 가져오기:radix-ui및@radix-ui/react-*; asChild->렌더 작업 예제 포함; Portal>Positioner>Popup; positioner FORWARD 규칙; part 이름 변경), 패밀리별 props 테이블(overlays.md,menus.md,form-controls.md,disclosure.md,display-misc.md), 데이터 속성/CSS 변수 재작성을 위한class-mapping.md, 정확한 대상 형태를 위한wrapper-shapes.md(툴팁 화살표, SubContent 기본값, select 해부학)를 사용하여 변환합니다.
모드
점진적 (기본값). "아코디언 마이그레이션" = 하나의 컴포넌트, strangler-fig 패턴:
- 먼저 진행 중인 상태 감지: 기존
<component>-base.tsx, 소비자가 이전/새 가져오기로 분할됨. 파일이 상태입니다; 재시작하지 말고 이어서 진행하세요. - 컴포넌트가 여전히 radix에 있는 다른 ui 래퍼를 가져오는 경우(select -> button), 중단하고 먼저 하향식으로 해당 래퍼를 마이그레이션할 것을 권장합니다.
- 마이그레이션된 버전을
<component>-base.tsx에 작성(원본은 건드리지 않음; 위 전략에 따라 URL로 가져온 골든 페어 내용 또는 수동 변환); 타입체크. 소비자를 한 번에 하나씩 재지정(가져오기 +consumer-props.md의 호출 사이트 props); 각각 타입체크. 어떤 소비자도 원본을 가져오지 않으면: 삭제,-base-> 원본으로 이름 변경, 가져오기 되돌리기, 최종 확인, 커밋. 프로젝트에서 마지막 radix 래퍼가 최종화되면,components.json을base-<style>로 뒤집고 radix 의존성을 제거하세요.
전체 프로젝트 (명시적으로 요청된 경우에만): 동일한 컴포넌트별 작업을 의존성 순서로 수행(리프/공유 래퍼인 button과 label 먼저). 래퍼 후, consumer-props.md에 대해 모든 앱 코드를 검사 — 호출 사이트 변경 표면이 asChild보다 훨씬 큽니다. 그런 다음 radix 의존성 제거, 설치, 전체 빌드.
엄격한 규칙
- 비-radix 라이브러리나 그 래퍼를 절대 건드리지 마세요: 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 (dir이 아닌directionprop). Popover Anchor와 NavigationMenu Indicator는 동등한 것이 없습니다: non-functional 통과 + 플래그. button.tsx는 실제@base-ui/react/button프리미티브로 마이그레이션되며, 절대 수제 useRender 래퍼로 하지 않습니다.- 동작 차이는 플래그 표시되며, 절대 조용히 패치되지 않습니다(탭 수동 활성화, 클릭 시 메뉴 항목 닫힘 없음, nav-menu 50ms 지연). 대상은 shadcn base 레지스트리와 일치하는 관용적인 Base UI입니다.
- 정직한 보고: 건너뛰거나 되돌린 파일은 마이그레이션된 것으로 표시되지 않고 플래그 표시됩니다. 사전 존재하는 실패는 사전 존재하는 것으로 명명됩니다.
확인 및 보고
파일별 타입체크, 배치별 빌드, 마지막에 기준과 비교한 전체 빌드.
보고서는 프로젝트 루트의 .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개의 래퍼가 Radix에 남아 있습니다").
각 .migration/<component>.md는 정확히 이 구조를 사용합니다 (공개적으로 문서화됨; 보고서는 일치해야 함):
# <component>
<날짜, 사용된 전략 (golden pair via CLI / merge / engine), 한 줄 평가>
## 변경됨
<건드린 모든 파일, 무엇이 변경되었고 그 이유; 주목할 만한 것은 file:line 포함. 잔여물 검사가 깨끗함을 확인: 이 컴포넌트의 파일에 대해 grep -n "radix-ui\|@radix-ui">
## 건드리지 않음
<관련되어 보이지만 의도적으로 건드리지 않은 파일, 이유 포함(cmdk/vaul/sonner는 radix가 아님; 관련 없는 변경 등)>
## 동작 변경사항
<컴파일은 잘 되지만 다르게 동작하는 차이점; 플래그 표시되었으며, 절대 패치되지 않음(탭 활성화, 메뉴 클릭 시 닫힘, 지연...). 없으면 빈 섹션>
## 수동 확인
<이 프리미티브 패밀리에 대한 짧은 수동 QA 체크리스트: 다이얼로그의 포커스 리턴, 메뉴/선택의 키보드 탐색 + 유형어헤드, 툴팁 지연 느낌, 슬라이더 커밋 이벤트. 구체적인 단계, 1분 클릭>