


ネイティブとWebで動くTypeScriptアプリを、リポジトリの決まりどおりに作る
ネイティブとWebの両方で動くTypeScriptアプリを作る・直す時に使う。リポジトリのスクリプトとフォルダの分け方に従い、共有のコンポーネントを使い回し、画面の文字を翻訳できる形にして、関係するすべてのプラットフォームでテストしてから終える。
このAIルールを使うには
## テスト・lint・ビルドを、リポジトリのスクリプトで動かす
対応バージョンとコマンドについては、パッケージマニフェストとリポジトリのスクリプトを正本として扱う。現在サポートされているAPIを優先し、対象範囲で安全に実施できる場合は、レガシーまたは非推奨のパターンを置き換える。
次の処理にはパッケージマネージャーのスクリプトを使用する。
- 開発サーバーと、サポート対象の各プラットフォームの起動
- 単体テスト、lint、型チェック、フォーマット
- Webビルドとネイティブプロジェクトの生成
リポジトリのスクリプトが存在する場合、基盤となるテスト、lint、型チェック、フォーマットの各ツールを直接呼び出してはならない。スクリプトには、直接実行するコマンドでは省かれる設定や準備処理が含まれている場合がある。
ローカライズの抽出やコンパイルをCIが担当している場合、それらのコマンドを実行してはならない。翻訳対象のソース文字列を追加または更新し、カタログと実行時の翻訳の更新は定期パイプラインに任せる。
コードを変更する前に、対象プラットフォーム、検証用の正規スクリプト、生成物をローカルと自動化のどちらが管理するかを特定する。
## 新しい画面やコンポーネントを、決まったフォルダに置く
共有するプラットフォーム非依存のUI要素は`components`に配置する。ページ全体のルートは`screens`に配置する。複数の画面にまたがってコンテキスト、データアクセス、コンポーネント、ユーティリティを組み合わせる大きな機能には`features`を使用する。
非公開のコンポーネントや状態を持つ複雑な画面では、画面用ディレクトリを作成し、それらの依存要素を同じ場所に配置する。複数の利用側で必要になるまでは、コードを共有フォルダへ昇格させてはならない。
サーバー状態のフック、単純なUI設定、セッション状態、永続化は、明確に分かれた状態モジュールに置くか、機能固有の状態をその利用側と同じ場所に配置する。無関係な責務を混在させた大きなファイルは避ける。
特定の機能に属さない再利用可能なユーティリティ、APIクライアント、フォーマッター、定数には`lib`を使用する。既存の責務に当てはまる場合は、ソース直下に新しいディレクトリを追加しない。
レガシーなviewディレクトリへ新しい作業を追加してはならない。移行がタスクに含まれていない限り既存のレガシーコードは維持する一方、新しい画面、コンポーネント、機能は現行の構造に配置する。
コンポーネント名にはPascalCaseを、通常のファイル、ディレクトリ、変数、関数にはcamelCaseを使用する。大規模なモジュールでは、兄弟要素としてコンポーネント、フック、ユーティリティを伴う`index.tsx`エントリを使用してもよい。小さなコンポーネントは1ファイルに収める。
任意の機能ドキュメントとテストは、大きなモジュールと同じ場所に配置する。コメントにはコードにすでに書かれている内容ではなく、判断の理由を書く。名前付き宣言にはdocblock、複数行の説明にはブロックコメント、短い注記に限って行コメントを使用する。
## デザインシステムと既存のコンポーネントで画面を作る
生のスタイルや重複するコンポーネントを作成する前に、既存のデザインシステムを使用する。テーマ非依存のatom、テーマatom、例外的な生のスタイルを、次の順序で組み合わせる。
1. flexとレイアウトのatom
2. 余白のatom
3. タイポグラフィのatom
4. テーマの色とサーフェス
5. 狭い範囲に限定した生のスタイル
条件付きスタイルにはプラットフォーム用のスタイルユーティリティを、レスポンシブレイアウトにはブレークポイント用フックを使用する。プラットフォーム条件をコンポーネントロジック内に分散させず、スタイルの合成内に収める。
コントロールを作成する前に、共有コンポーネントライブラリから適切なボタン、ダイアログ、メニュー、テキストフィールド、タイポグラフィのプリミティブを探す。その複合APIと、既存の正規の使用例に従う。
コンポーネントの宣言では、次に従う。
- key付きfragmentが必要でない限り、fragmentの短縮記法を優先する
- 関数宣言を優先する
- 引数リストでpropsを分割代入する
- 読みやすさを保てる場合は明示的なインラインのprops型を使用する
- 任意のpropsには妥当なデフォルト値を設定する
- 再利用可能なコンポーネントは、可能な限りプラットフォーム非依存に保つ
プラットフォームのアーキテクチャで制御入力の性能が低い場合、`defaultValue`を使う非制御テキストフィールドを使用する。実際にその動作が必要な場合に限って制御値を使用する。
ユーザー生成テキストには、例に含まれていなくても絵文字が入る可能性がある。ユーザーコンテンツではタイポグラフィシステムの絵文字安全機能を有効にし、既知の静的テキストの場合に限って省く。
## 画面に出る文字を、すべて翻訳できる形にする
ユーザー向けの文字列はすべて、プロジェクトの翻訳マクロまたは翻訳コンポーネントでラップする。名詞と動詞が同じ綴りを共有する場合など、単語に複数の意味があるときは翻訳者向けコメントやコンテキストを追加する。
翻訳フレームワークがサポートする最新のマクロエントリポイントを使用する。テーマなど別のサブシステムが同じ短い変数名をすでに使用している場合は、その翻訳関数に別名を付ける。
件数には翻訳フレームワークの複数形機能を、日付と時刻には日付フォーマッターを使用する。フレームワークによるフォーマットは有効な言語が変わるたびに再実行する必要があるが、ロケールを直接指定したフォーマットでは再実行されない場合が多い。
ユーザー向けテキストでは、タイポグラフィとして正しいUnicodeの約物を優先する。引用符やダッシュ前後の空白など、プロジェクトの約物規約を一貫して適用する。
生成されたロケール出力を手作業で編集せず、CIが担当する抽出やコンパイルのタスクをローカルで実行してはならない。ソースの変更にはラップ済みのメッセージと、正確な翻訳に十分なコンテキストを含める。
## サーバーのデータ、画面の移動、プラットフォーム別のファイルを扱う
リモートデータには既存のサーバー状態クエリライブラリを、単純なUI設定にはcontext形式の状態を使用する。設定は、読み取り用と更新用の対になるフックを通じて公開する。
共有のkeyヘルパーと引数オブジェクトを使ってクエリkeyを構築する。keyのルートはフック名と一致させる。クエリ用フック、ミューテーション用フック、キャッシュだけを変更するミューテーションヘルパーには一貫した名前を使用する。
stale timeは共有定数から選ぶ。カーソルページネーションにはinfinite queryを使用し、レスポンスのカーソルを次ページのパラメーターとして返し、ページの平坦化は利用境界でのみ行う。
永続化されたクエリデータの形が変わった場合は永続化バージョンを上げ、古いシリアライズ済みデータが破棄されて再取得されるようにする。
ミューテーション失敗時は、次に従う。
- 想定内のネットワーク障害はエラーログに重複して記録せず、ユーザーに通知する
- 型付けされたプロトコルまたはAPIエラーを明示的に処理する
- 想定外の障害は安全なメッセージとともに構造化ログへ送る
型付けされたルートパラメーターと、プロジェクトのナビゲーションヘルパーまたはフックを使用する。ルート設定、ルート定義、ルート型を同期させる。
プラットフォーム別実装は`.web`、`.native`、`.ios`、`.android`などのファイル名サフィックスで表す。基底モジュールのパスをimportし、実装はbundlerに選択させる。プラットフォームファイルの選択に条件付きのruntime importを使用してはならない。
プラットフォーム別実装は1つのモジュールディレクトリ内にまとめることを優先する。runtimeのプラットフォームフラグは動作にだけ使用し、モジュール解決には使用しない。深い相対パスではなく、リポジトリの絶対importエイリアスを使用する。
## ダイアログを閉じる時のタイミングのずれと、むだなメモ化を防ぐ
ダイアログを閉じた後に実行する必要があるすべてのアクションは、ダイアログ制御のclose callback内で実行する。先に閉じてから、別の場所へすぐに遷移する、別のoverlayを開く、影響を受ける状態を更新する、親callbackを呼び出すといった処理を行うと、close animationとの競合が起こり得る。
必要な順序は次のとおり。
1. closeメソッドを呼び出す
2. そのメソッドへcallbackを渡す
3. そのcallback内で、ナビゲーション、overlayの表示、状態更新、親callbackの呼び出しを実行する
プラットフォーム固有の複合コンポーネントを尊重する。ダイアログのhandleやメニューコンテナのitemはネイティブ専用の場合がある一方、close controlやメニューのdividerはWeb専用の場合がある。各分岐を通るプラットフォームでテストする。
コンパイラによる自動メモ化が有効な場合、`useMemo`や`useCallback`を先回りして追加してはならない。effectの依存値やReact以外のライブラリとの契約など、参照の安定性が具体的に必要な場合に限って使用する。
投機的な最適化のために可読性を犠牲にしてはならない。手動メモ化を追加する前に、実在する問題を測定するか、明示的な安定性要件を満たす必要がある。
## 終える前に、関係するすべてのプラットフォームでテストする
完了前に、次を行う。
1. すべての操作可能な要素にアクセシブルなラベルがあり、必要な箇所には有用なヒントがあることを確認する
2. ユーザー向け文字列がすべてローカライズされ、曖昧なメッセージにコンテキストが含まれていることを確認する
3. スタイリングでデザインシステムのatom、テーマ値、プラットフォーム用ユーティリティが一貫して使用されていることを確認する
4. サーバー状態、UI設定、セッション状態で、それぞれ意図した仕組みが使用されていることを確認する
5. 既存の共有コンポーネントを重複して作成していないことを確認する
6. propsとルートパラメーターに明示的な型があることを確認する
7. E2Eテストで必要な箇所に、安定したテスト識別子を公開する
8. テスト、lint、型チェック、フォーマット用のリポジトリスクリプトを実行する
9. 影響を受けるネイティブとWebのすべての動作をテストする
差分を確認し、レガシーディレクトリの新規使用、条件付きplatform import、生成されたローカライズファイルの直接編集、非制御入力と制御入力の間の退行、ダイアログを閉じた後の競合がないかを確認する。
実行できなかったプラットフォームや正規スクリプトについては、不足している前提条件を含めて報告する。単一プラットフォームの確認だけでクロスプラットフォームの完了を主張してはならない。
