componentize について
componentizeは、新しいUIを作る前にコードベースの再利用機会を調べるスキルです。既存コンポーネントやプロジェクト境界、規約を確認し、適応・移行・抽出の方針を検討します。
主な機能
- 01リポジトリ構成とUIスタックを調査
- 02重複コンポーネントやマークアップを検索
- 03既存コンポーネントの適応を優先
- 04コンポーネントAPIと移行先を計画
- 05サーバーとクライアントの境界を考慮
活用シーン
- 01複数ページにあるUIの重複を監査
- 02実装前に共有コンポーネントの配置先とAPIを決定
- 03サーバー境界を保つため操作UIを分離
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ howells/ skills --skill componentizeインストール後、Claude Code で/componentizeを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlreferences/collection-migration.mdpackage-extraction.mdscripts/find-god-files.py詳細
コンポーネント化
新しい UI を作成する前に、コードベースを調査して再利用の機会を見つけるため、このスキルを使用します。重複を減らし、共有コンポーネントを標準化して、アプリのコードが props、スロット、コンポジション、明確なパッケージ境界を通じてそれらのコンポーネントを利用できるようにすることが目的です。ユーザーからデザインシステムのプロジェクトを明示的に依頼されない限り、UI 全体を一元化しないでください。
開始
呼び出されたら、次の手順を実行します。
componentizeスキルを使用していることを伝えます。- 編集する前にリポジトリの構成を特定します。
- パッケージマネージャーとワークスペース設定
- アプリとパッケージ
turbo.jsonがあるかどうか- 共有 UI パッケージがすでにあるかどうか。一般的な場所は
packages/ui、packages/design-system、packages/components、libs/uiです - UI フレームワーク、スタイリングシステム、アイコンライブラリ、テスト設定、Storybook またはサンプル
- ユーザーが場所、見た目、操作方法で対象を指定している場合は、広範囲に検索する前にその対象を特定します。
- 何かを作成する前に検索します。
rg、rg --files、インポート関係を使って、既存のコンポーネント、繰り返し使われているマークアップ、繰り返し使われている Tailwind クラス文字列、コピーされたフォームコントロール、重複したシェル、バリアントの不一致を探します。 - ユーザーが実装を依頼している場合は、まず抽出対象、標準となる配置先、公開 API、移行する呼び出し箇所、検証計画を示します。その後、変更範囲を絞って編集し、検証します。分析を依頼されている場合は、優先順位を付けた抽出候補の監査結果を返します。
基本原則
追加よりも既存のものを適応させることを優先します。
- 既存のコンポーネントをそのまま再利用します。
- 重複したマークアップや一度限りのローカルコンポーネントを、そのパターンをすでに担っている既存のコンポーネントに置き換えます。
- 既存のコンポーネントに prop、バリアント、スロット、レンダープロパティ、コンポジションポイントを追加します。
- 2 つ以上のローカル実装から共有プリミティブを抽出します。
- 既存のコンポーネントを適切に適応できない場合に限り、新しい共有コンポーネントを作成します。
- 再利用がアプリやパッケージの境界を越え、候補の API がドメインに依存しない場合に限り、新しい共有 UI パッケージを作成します。
コピー、余白、アイコン、色、わずかなレイアウトだけが異なるコンポーネントを並行して作らないでください。コンポーネントが曖昧になったり、過剰な責務を担ったりしない限り、こうした差異は単一の API にまとめます。 既存のコンポーネントが動作とアクセシビリティの要件をすでに満たしている場合は、別のコンポーネントを抽出するのではなく、そのコンポーネントを呼び出すように移行します。
アトミックコンポーネントの基準
有用な最小単位のコンポーネント境界を目指します。複合的なプロダクト UI を抽出する前にプリミティブを抽出し、複合コンポーネントはマークアップやクラス文字列を複製せず、プリミティブに依存するようにします。
次の階層を優先します。
- トークンまたはクラスヘルパー:共有する余白、タイポグラフィ、色、フォーカスリングのルール
- プリミティブ:Button、Input、Badge、TooltipTrigger、DialogContent
- 複合プリミティブ:Field、Menu、Tabs、Select、DataList
- 複合サーフェス:Toolbar、EmptyState、PageHeader、FilterBar
- アプリ固有のコンポジション:ルートを認識するシェル、ビジネスワークフロー、説明文が多いパネル
アトミックであることは、ファイルを必要以上に小さくすることではありません。安定した責務が 1 つで、アクセシビリティの要件が 1 つで、隠れたドメインロジックがなく、他の呼び出し箇所にプロダクト固有の前提を持ち込まずに再利用できる API を備えている場合、そのコンポーネントはアトミックです。
バリアントとコンポジションの規約
新しい規約を導入する前に、リポジトリにすでにあるコンポーネントの規約に従ってください。
- リポジトリで shadcn 形式のプリミティブを使っている場合、セマンティックなバリアントとサイズには
class-variance-authority(cva)とVariantProps、利用側のclassNameのマージにはcn/twMerge、ボタン、リンク、トリガー、メニュー項目などのポリモーフィックなリーフコンポーネントには RadixSlot/asChildを優先してください。 - リポジトリで
@base-ui-components/reactの Base UI を使っている場合、そのrenderプロパティによるコンポジションモデルを優先してください。同じポリモーフィズムが必要なカスタムプリミティブでは、独自のasChildクローンを作らず、Base UI のuseRenderとmergePropsのパターンを使ってください。 - リポジトリで古い MUI Base 形式のコンポーネントを使っている場合、Radix
Slotや Base UI のrenderパターンを混在させず、そのslotsとslotPropsのカスタマイズモデルに従ってください。 - リポジトリで
tailwind-variants、Stitches、Vanilla Extract、CSS modules、Panda、その他の確立されたバリアントシステムを使っている場合、1 つの抽出のためにcvaを追加せず、そのシステムを拡張してください。 - 確立されたパターンがない場合、複数の呼び出し箇所でセマンティックなバリアントやサイズが実際に使われているコンポーネントに限り、
cvaを使ってください。バリアントの組み合わせがない単発のプリミティブには、通常のcn(...)によるクラスのマージを使ってください。
ポリモーフィックなコンポーネントについては、次の点に従ってください。
- Slot 互換ライブラリを使っており、コンポーネントが props、イベント、ref、アクセシビリティ属性、data 属性を 1 つの子に安全に渡せる場合に限り、
asChildを公開してください。 - リポジトリで
@base-ui-components/reactの規約を使っている場合、またはコンポーネントに本当に render-prop によるコンポジションが必要な場合に限り、Base UI 形式のrenderを公開してください。 - 必須のネイティブ専用 props、内部 DOM 構造、任意の要素では無効になるアクセシビリティ上のセマンティクスを持つコンポーネントでは、ポリモーフィズムを避けてください。
- コンポーネントの API で、デフォルトの要素と必須の子/ref の動作を文書化するか、型で示してください。
サーバーとクライアントの境界
Next.js App Router などの React Server Component フレームワークでは、デフォルトで Server Component を優先し、操作に必要な最小限の範囲にクライアント境界を保ってください。
RSC 対応アプリでコンポーネント化する場合:
'use client'が記述されたファイルを特定し、UI を抽出または移動する前に、そのインポートツリーを確認してください。- ネストされたコントロールの 1 つに state、effect、ブラウザー API、context、イベントハンドラーが必要という理由だけで、大きなルート、レイアウト、シェル、テーブル、フォーム、パネルを Client Component に変えることは避けてください。
- インタラクティブな部分を実用上最小の Client Component として抽出し、データ取得、サーバー専用モジュール、シークレット、ルーティングパラメーター、静的マークアップ、非インタラクティブなレイアウトを担う Server Component 内に配置してください。
- Server Component から Client Component へはシリアライズ可能なデータを渡してください。サポートされている Server Function の参照は渡せますが、通常のコールバック、サポートされていないクラスインスタンス、サーバー専用オブジェクト、その他のシリアライズできないデータは境界を越えて渡さないでください。
- 操作がモーダル、開閉パネル、タブの状態、ポップオーバーなどシェルのみに関わる場合は、
childrenまたは明示的なスロットを使って、Server Component で描画した UI を小さなクライアントラッパー内に視覚的にネストしてください。 - プロバイダーやサードパーティ製のクライアント専用ラッパーを、アプリ、レイアウト、ページ全体をデフォルトで囲むのではなく、可能な限りツリーの深い位置に配置してください。
server-onlyとclient-onlyのガードインポートがある場合は維持し、その規約をリポジトリですでに使っている場合、または境界の誤りが具体的なリスクとなる場合に限り追加してください。
サーバーファイルからクライアント専用コンポーネントをインポートできないために重複コードが存在する場合、すべてをクライアント側にすることで解決しないでください。可能であれば共有するプレゼンテーション用プリミティブを環境非依存のコンポーネントとして分離し、そのうえでインタラクティブな動作を小さな Client Component で包んでください。
調査
ファイル名だけでなく、コードを読んでください。
画面上の対象の特定
ユーザーが「右側のものをコンポーネント化して」「左のパネル」「あのヘッダー」「カードのグリッド」「サイドバー」のように指示した場合は、次の手順に従います。
- 利用できる場合は、実行中のアプリを確認します。環境で推奨されているブラウザーまたはスクリーンショットツールを使用します。
- 画面上の位置、近くにあるテキスト、役割、ランドマーク、ルート、インタラクションから、表示されている領域を特定します。
- 可能であれば DOM のラベルやクラスを確認して、その領域をソースに対応付けます。そのうえで、特徴的なテキスト、aria ラベル、テスト ID、コンポーネント名、ルートファイル、近くにあるアイコン名を検索します。
- 該当する可能性のある領域が複数ある場合は、編集前に簡潔な確認質問を 1 つします。
- 対応付けができたら、監査または実装のチェックポイントで対象を明示します。
- 画面上の対象:「右側のインスペクターパネル」
- ソースの所有元:
path/to/file.tsx - 抽出候補:
InspectorPanel - 見つかった利用箇所/呼び出し箇所
レイアウト上の方向だけで推測しないでください。抽出する対象は、ソースの証拠と結び付ける必要があります。
確認項目:
package.json、ワークスペースファイル、turbo.json、tsconfig*、ビルドスクリプト、lint スクリプト、テストスクリプト- アプリのルート/ページと機能ディレクトリ
components/、ui/、design-system/、shared/、lib/、パッケージのエントリーポイント- ローカルコンポーネント、パッケージのコンポーネント、shadcn/radix/headless ライブラリ、アイコンライブラリ、CSS ユーティリティからのインポート
- 繰り返し使われるクラス文字列と JSX 構造
- 重複するローカルコードに既存のコンポーネントの props とアクセシビリティの仕様がすでに対応しているか
- コンポーネントが
className、children、asChild、バリアント props、サイズ props、状態 props、アクセシブルなラベルを受け付けるか - 既存のスタックで
cva、tailwind-variants、Radix のSlot/asChild、Base UI のrender/useRender、MUI Base のslots/slotProps、CSS モジュール、または別のバリアント/合成規約が使われているか - フレームワークで Server Components、
'use client'ディレクティブ、server-only/client-onlyガードインポート、サーバーアクション、ルートハンドラー、またはシリアライズ可能な Server-to-Client props が使われているか
検索に便利なコマンド:
rg --files -g '*.{tsx,jsx,ts,js}' | rg '(^|/)(components|ui|design-system|shared|app|pages|src)/'
rg "className=['\"][^'\"]{60,}['\"]" -g '*.{tsx,jsx}'
rg 'className=\{|cn\(|clsx\(|cva\(|tv\(|data-slot=' -g '*.{tsx,jsx}'
rg '(Button|Card|Modal|Dialog|Input|Select|Badge|Avatar|Tabs|Table|Toast|Tooltip)' -g '*.{tsx,jsx}'
rg '@radix-ui|@base-ui|@mui/base|class-variance-authority|tailwind-variants|Slot|asChild|useRender|slotProps|variant' -g '*.{tsx,jsx,ts,js}'
rg "['\"]use client['\"]|['\"]use server['\"]|server-only|client-only|useState\\(|useEffect\\(|window\\.|document\\.|localStorage" -g '*.{tsx,jsx,ts,js}'
rg --files -g '*.{tsx,jsx}' | sed 's#.*/##' | sort | uniq -d
パスを指定して検索する前に、ソースルートを特定します。ソースディレクトリが決まっていると想定しないでください。リポジトリを確認し、app、apps、src、packages、libs、frontend、web など、実際に存在するディレクトリだけを検索します。
インポート関係を調べるには、まずテキスト検索を使います。
rg "^import .* from ['\"](\.|@|~|[A-Za-z])" -g '*.{ts,tsx,js,jsx}'
rg 'export .* from|export \{' -g 'index.{ts,tsx,js,jsx}' -g 'package.json'
madge、dependency-cruiser、パッケージ境界チェッカーなどのグラフツールは、すでにインストールされている場合、またはリポジトリで明らかに使用されている場合に限って使います。ユーザーから依頼されていない限り、監査を完了するためだけに新しいグラフツールを追加しないでください。
Turborepo の判断
プロジェクトが Turborepo の場合:
- 既存の UI パッケージがあり、その用途が作業内容に合っている場合は、それを使用します。
- 確立された規約がない場合は、
packages/uiを標準的な配置先として優先します。 - パッケージのエクスポートは、範囲を絞り、意図を明確にします。
- 利用側のアプリからは、パッケージの公開 API を経由してインポートします。
@repo/ui/buttonのように意図的に設けたサブパスエクスポートは問題ありませんが、非公開のソースファイルを直接インポートしてはいけません。 - パッケージ間でインポートする場合は、不足しているパッケージ依存関係を追加します。
UI パッケージを作成または変更する場合は、次を確認します。
- JavaScript と型のエントリーポイントに関する
package.jsonのexports - CSS のインポートを維持する必要がある場合の CSS/スタイルのエクスポートと
sideEffects - 利用側でソースのトランスパイル、パッケージのビルド出力、または TypeScript の直接エクスポートが必要かどうか
- 必要に応じた、パッケージ内のクラスに対する
@sourceを含む Tailwind v4 のソーススキャン - React、フレームワークのパッケージ、Radix/ヘッドレスライブラリ、Tailwind ヘルパー、アイコンのピア依存関係
- パッケージレベルの使用例、ストーリー、または利用側からのインポートのスモークテスト
共有パッケージがない場合は、境界を越えた再利用を裏付ける根拠がある場合に限り、作成を推奨します。
- 2 つ以上のアプリで UI のプリミティブやレイアウトが重複している
- コンポーネントがすでにパッケージ間でコピーされている
- アプリのコードが別のアプリの境界を越えてインポートしている
- 再利用可能なデザインシステムが複数箇所で形成されつつある
- UI の重複により、一貫したアクセシビリティ、テーマ設定、またはインタラクションの実現が妨げられている
- 候補となる API が、既知のすべての利用側に対応できるほどドメインに依存していない
再利用が 1 つのアプリ内にとどまる場合、重複がわずかな場合、または API がまだ製品固有の場合は、アプリ内の共有フォルダーを標準とします。1 つのアプリだけのためにパッケージを作成することは避けます。ただし、ローカルのコンポーネントツリーがすでに十分大きく、パッケージ境界を設けることで所有範囲が明確になる場合は除きます。
抽出対象
再利用価値が高く、ドメイン結合度が低いコンポーネントを優先します。
- プリミティブ:Button、IconButton、LinkButton、Input、Textarea、Select、Checkbox、Switch、RadioGroup、Badge、Avatar、Tooltip
- UI サーフェス:Card、Panel、EmptyState、ErrorState、LoadingState、Dialog、Sheet、Popover
- データ表示:Table、DataList、Stat、MetricCard、Pagination、Tabs
- フォーム:Field、FieldLabel、FieldError、FormSection、SearchInput
条件付きの対象:
- レイアウトシェル:AppShell、PageHeader、Section、Toolbar、Sidebar、Breadcrumbs
レイアウトシェルや、製品固有のデータ取得、認証、分析、機能フラグ、ルートの前提、ナビゲーション状態、権限、ビジネス向け文言を含むコンポーネントは慎重に扱います。プレゼンテーション部分を抽出し、ドメインのオーケストレーションはアプリ内に残します。
コンポーネント API のルール
共有コンポーネントは、次の要件を満たす必要があります。
- 特別な理由がない限り、
classNameを受け取ります - レイアウトのマージンは呼び出し側で管理します。コンポーネント側で内部のパディングや間隔を持たせてもかまいません
- 実際の意味の違いに対してバリアントを用意し、個別の見た目の違いすべてに用意することは避けます。
cvaなどを使う場合、バリアント名は呼び出し側に依存するものではなく、意味に基づく名前(intent、tone、size)にします - 新しいプロパティやバリアントは、実際に確認できる呼び出し側の違いに対応させます。別の具体的な利用者が必要とするまでは、推測に基づくオプションは追加しません
- 複雑なコンテンツには、
children、スロット、またはコンポジションを優先します - スタックが対応している場合、プリミティブは末端的でスロットに対応しやすいものにします。呼び出し側で組み合わせられるアイコン、ラベル、リンク、レイアウト用ラッパーをプリミティブに組み込むことは避けます
- インタラクティブ要素またはフォーカス可能な要素をラップするプリミティブでは、ref を転送し、有効な DOM プロパティをスプレッドします
- イベントハンドラー、ref、
className、style、data 属性、ARIA プロパティは、リポジトリで既に使われているヘルパー(cn、mergeProps、Slot など)を使ってマージします - アクセシビリティ上の名前、フォーカス状態、キーボード操作、ARIA の契約を維持します
- 環境に依存しないプリミティブでは、クライアント専用 API を直接使わない限り、
'use client'を付けません。状態を持つ動作が必要な場合は、共有コンポーネント全体をクライアント側にするのではなく、薄いクライアントラッパーを作成します - アプリ専用モジュール、ルートヘルパー、環境変数、サーバー専用コードのインポートは避けます
- 依存関係のサイズを抑えます。適切な場合、アプリのフレームワークはピア依存関係にします
1 つのコンポーネントに設定項目を増やしすぎて、互いに無関係な 5 つのデザインを隠すことのないようにしてください。バリアントの概念が衝突する場合は、コンポーネントを分割するか、より小さなプリミティブを切り出します。
所有権が不明確なものを隠すための逃げ道として、asChild、render、スロットプロパティを使わないでください。これらは、有効なセマンティクスとコンポジションを維持するためのものであり、無関係なコンポーネントを 1 つの API の背後に隠すためのものではありません。
インポート、プロパティ、子要素が移行先の環境で有効かどうかを確認するまでは、コンポーネントをサーバーとクライアントの境界を越えて移動しないでください。
UI 以外の 2 つの作業
ここまでの作業は UI に関するものです。肥大化したファイルの分割と、コードの独立したパッケージへの移行にも、同じ事前調査と同じルールが当てはまります。つまり、統合する前に重複があることを確認します。そのため、ここで扱います。
巨大なファイルを分割する
非常に多くの責務を抱えていて、何かを変更するたびにすべてを読む必要があるファイルです。長さだけを理由に分割してはいけません。 複数の責務がある、ロジックが重複している、変更理由が安定していない、テストしにくい分岐がある、または公開インターフェースを小さくできる場合に分割します。
スキャナーで候補を見つけてから、内容を確認してください。スキャナーはヒューリスティックに基づいてファイルを順位付けしますが、判断するのはあなたです。
python3 scripts/find-god-files.py /path/to/repo
python3 scripts/find-god-files.py /repo --min-score 20 --duplicate-window 10
python3 scripts/find-god-files.py /repo --include-tests --json
スコアではファイルの長さと責務に関するシグナルを評価し、デフォルトではスコアが35に達したファイルを報告します。デフォルトのしきい値では、300行以上のファイルは長さだけで一覧に含まれることがあります。判断する前に、検出理由とソースを確認し、実際に複数の責務が混在しているかどうかを見極めてください。--min-score を下げると、より多くの候補を検出できます。
確認した候補はそれぞれ、巨大なコンポーネント(レンダリング、データ整形、エフェクト、変更処理、バリデーション、サブビュー制御が1つのファイルにまとまっている)、巨大なスクリプト(引数解析、I/O、ドメインロジック、フォーマット処理、副作用が混在している)、巨大なモジュール(UI以外の、互いに無関係な複数の責務がある)、または重複に分類します。
その後、候補を1つずつ、次の順序で分割し、各ステップの後に最小限の関連チェックを実行します。
- 純粋なヘルパー。
- 重複ロジック。呼び出し元を移動する前に分割します。
- 状態が絡み合っている場合は、子コンポーネントより先にフックと状態機械。
- JSXが大きい場合は、レイアウトシェルより先にリーフサブコンポーネント。
- スクリプトでは、I/Oアダプターをドメインロジックから分離します。
テストが通るまでは公開インポートを安定させ、その後でバレルとエクスポートを整理します。
コンポーネントでは、再利用されている場合や実際に分岐を減らせる場合に限り、データ取得をフックに移します。繰り返し使われるJSXは、汎用的な Content や Wrapper ではなく、名前付きのドメインコンポーネントに移します。状態は、インタラクションを所有するコンポーネントの近くに保ちます。1つの小さな動作を理解するために、多数の浅いファイルを開かなければならない構成は避けてください。
スクリプトでは、引数解析、設定、変換、I/O、出力フォーマットを分離します。決定論的なロジックは、ユニットテスト可能な関数の中に置きます。エントリーポイントは薄く保ち、終了コードとストリームの動作を維持してください。
重複については、コピーされたコードが同じ状態を保つべきものだと確認してから統合します。たまたま似ていてもドメイン上の意味が異なるコードは分けたままにし、共有スキーマを切り出すのは、それらが同じ概念を表す場合に限ります。
スタンドアロンパッケージの抽出
対象が共有フォルダーではなく、公開可能なパッケージまたは独立したリポジトリの場合も、上記のパッケージに関する判断が適用され、以下の手順で残りを進めます。検証チェックリストの全文は、references/package-extraction.md を参照してください。
編集前に境界を定義します。 含めるもの:コンポーネント、フック、ユーティリティ、その型、スタイル、アセット、フィクスチャ、テスト、本質的なサポートモジュール、意図した API を示す例。含めないもの:ルート、認証およびセッションのコード、データベースクライアント、環境へのアクセス、製品のコピー、分析、機能フラグ、無関係なデザインシステムのラッパー、古いインポートパスとの互換性を保つためのシム。抽出によって隠れた依存関係が明らかになった場合は、編集前に報告し、整合性のある最小限の境界を選びます。
ファイルを作成する前にパブリック API を設計します。 パッケージ名、エントリーポイント、エクスポートするシンボル、ピア依存関係とバンドルする依存関係の区別、スタイリングの契約、アセットの処理、サーバー/クライアントのランタイム制約を決めます。狭い API を優先します。存在するという理由だけですべての内部ファイルをエクスポートすることが、パッケージを保守不能にする原因です。
公開可能な npm パッケージでは、明示的な exports マップ、出力または参照される型、files フィールドが必要です。また、利用側が用意する必要のある React、フレームワーク、スタイリングライブラリはピア依存関係として指定します。
別のリポジトリへの移行では、まずコピーし、削除が明示的に依頼されない限り、元の内容はそのまま残します。同一リポジトリ内で抽出する場合は、対象範囲内の呼び出し元を移行し、置き換えられる実装を原則として削除します。依頼された場合、または既存の契約で必要な場合に限り、意図的な段階的移行を維持し、その管理責任を記録します。独立した移行先リポジトリでは、別途採用の依頼があるまで、ソースアプリを変更しないこともできます。
明示的な承認なしに、自動で公開、タグ付け、プッシュ、リリース作成を行わないでください。また、ライセンス、公開範囲、レジストリのスコープ、パッケージの所有権を黙って変更しないでください。
ワークフロー
1. 棚卸し
簡潔な棚卸しを作成します。
- 既存の共有 UI の配置場所
- 重複しているパターンと、その出現箇所
- API を変更せずに重複コードを置き換えられる既存コンポーネント
- props を使って調整すべきコンポーネント
- 抽出すべきコンポーネント
- アプリ内にとどめるべきコンポーネント
2. 正式な配置先を選択
候補ごとに、次のいずれかを選びます。
- 既存コンポーネント:API を調整し、呼び出し箇所を移行する
- 既存の UI パッケージ:コンポーネントをそこへ移動または追加する
- 新しい UI パッケージ:パッケージ作成の基準を満たす場合に限り、実用最小限のパッケージを作成する
- アプリ内共有フォルダー:再利用がアプリ内に限られる場合のみ使用する
- 対応なし:重複が表面的なもの、またはドメイン固有のものである
3. API を設計
ファイルを移動する前に、公開コンポーネント API を定義します。
- コンポーネント名とインポートパス
- props とバリアント。バリアントの実装に
cva、tailwind-variants、CSS モジュール、既存のローカルヘルパーのどれを使うかを含む - コンポジション/スロットモデル。ポリモーフィズムに Radix/shadcn の
asChild、Base UI のrender/useRender、MUI Base のslots/slotPropsを使うか、ポリモーフィズムを使わないかを含む - サーバー/クライアントの境界。コンポーネントをサーバーレンダリングのままにするか、環境に依存しない状態にするか、小さな
'use client'ラッパーを必要とするかを含む - スタイリング/テーマ設定の契約
- アクセシビリティの契約
- 想定される使い方を示す例またはテスト
4. 小さな単位で実装
整合性のあるグループを一度にひとつずつ抽出します。リポジトリ内に正式なバージョンが二つ残らないよう、インポートと呼び出し箇所をすぐに更新します。
例や利用箇所が多いコレクションでは、安定した比較、ファミリー単位の移行、網羅性の確認のために references/collection-migration.md を読み込みます。
ビジュアルの方向性や仕上げが必要な場合に限り chiaroscuro を読み込みます。重複したスタイリングがタイポグラフィに関するものであれば typecase を読み込みます。重複排除と共有コンポーネントに関する判断はこのスキルに属し、コンポーネント化のワークフローが適用範囲を決定します。
5. 検証
意味のある範囲で、最も限定的なチェックを実行します。
- 変更したパッケージ/アプリの型チェック
- 変更したパッケージ/アプリのリンティング
- 関連するテスト
- 共通 UI のサンプルがある場合は、Storybook/サンプルのビルド
- パッケージのエクスポートまたはインポートを変更した場合は、利用側アプリのビルド
- 共通パッケージを変更した場合は、エクスポートされた JavaScript、型、スタイルが解決されることを証明する、利用側からのインポートのスモークテスト
- 実行可能なアプリまたは Storybook がある場合は、影響を受けるページ/コンポーネントのレンダリング UI のスモークチェック
- ダイアログ、セレクト、タブ、ボタンなどのインタラクティブなプリミティブのキーボード/フォーカスチェック
- 変更によって
'use client'の境界が移動する場合、サーバーコードからクライアント専用ライブラリをインポートする場合、または Server Components が使用するパッケージのエントリーポイントが変更される場合は、バンドルまたはビルドのスモークチェック
チェックを実行できない場合や、範囲が広すぎる場合は、検証できたこととできなかったことを正確に記載してください。
出力
分析を返すときは、次の形式を使用します。
## Reuse Audit
### Existing Shared Surface
- `path`: purpose, health, import pattern
### Highest-Value Opportunities
| Priority | Pattern | Evidence | Recommendation | Why |
|---|---|---|---|---|
### Proposed Shared API
- `ComponentName`: props, variants, slots, import path
### Package Plan
- Use existing package / create `packages/ui` / keep app-local
- Export strategy
- Dependency changes
- Server/client boundary notes for RSC-aware apps
### Migration Plan
1. Extract/adapt component
2. Migrate call sites
3. Delete duplicate implementations
4. Validate
### Risks
- Coupling, accessibility, styling, or package-boundary concerns
「最も価値の高い機会」テーブルの根拠には、代表的な file:line の参照、出現回数または呼び出し箇所の数、現在の所有者/配置場所、そして変更内容がそのまま再利用、適応、抽出、移動、対応不要のいずれであるかを含めてください。
ガードレール
- まず再利用できるものを検索し、新しいコンポーネントの作成から始めないでください。
- ユーザーが段階的なロールアウトを求めていない限り、移行後に正規の実装を重複させたままにしないでください。
- ドメインの動作を共通 UI パッケージに移さないでください。
- インポートを通すためにパッケージの境界を緩めないでください。
- ユーザーがビジュアルデザインの変更を求めていない限り、コンポーネント化と同時に広範な再設計を行わないでください。
- 既存のスタックとトレードオフを確認せずに、コンポーネントライブラリの依存関係を追加しないでください。
完了チェック
完了する前に、以下を確認してください。
- 新規または変更したすべての共通コンポーネントに、明確な利用側がある
- 重複する実装を削除したか、残した理由を明示している
- インポートが正規の公開パスを使用している
- props/コンポジションが、過剰に特化することなく既知のバリエーションをカバーしている
- 検証を実行したか、実行できなかった場合はその不足を報告している