検索/別のWebフレームワークと同じ動きをする互換レイヤーを作る

別のWebフレームワークと同じ動きをする互換レイヤーを作る

別のWebフレームワークの動きを、複数のランタイムで同じように作り直す時に使う。先にそのフレームワークのテストを移し、リクエストの処理順とランタイムの境目を守り、ファイルパスの書き方をそろえ、全部のチェックに通ったpull requestだけでマージする。

このAIルールを使うには
## まねするフレームワークのテストとコードを、仕様として使う 互換性を根拠の問題として扱う。対象フレームワークのテストと実装は、直感よりも確実に期待動作を定義する。 機能追加やバグ修正のたびに、次を行う。 1. 関連する名前、エラー、無効な入力、境界事例を含め、上流の単体テストとエンドツーエンドテストを広く検索する。 2. 対応する実装を読み、処理順序、既定値、失敗時の動作を理解する。 3. コードだけでは動作が意図的か判断できない場合、issueや過去の設計議論を検索する。 4. 変更を実装する前に関連するテストケースを移植し、各移植先から上流テストへリンクする。 5. 変更説明に根拠を記録する。 ソースとテストを素早く検索するには、ローカルのshallow cloneを使う。リモートコード検索は、cloneを利用できない場合か、最新の議論を確認する必要がある場合に限って使う。 セキュリティ、デプロイ、プラットフォーム上の理由で互換レイヤーを意図的に異なる動作にする場合は、その判断を明示的に記録する。偶発的な相違を機能であるかのように扱ってはならない。 ## 先に失敗するテストを書き、開発用・本番用・edgeの入口をまとめて直す まず対象を絞った失敗するテストを書き、検証済みの契約を満たす最小の変更を実装する。 - 変更したサブシステムのテストファイルと、直接結合しているテストを実行する。 - 統合動作にはfixture入力を追加し、例はユーザー向けの実演用として残す。 - 反復中はテスト名フィルターを使う。 - 変更が共有ルーティング、ビルド基盤、仮想モジュール、共通テストヘルパーに影響しない限り、全テストスイートの実行はCIに任せる。 - 共有基盤を変更した場合、ローカル検証は影響を受ける横断領域だけに広げる。 検証を所有範囲に対応させる。シムにはシムのテスト、ルーティングにはルート順序のテスト、キャッシュにはキャッシュのテスト、ビルドまたはデプロイの変更にはビルドテストが必要になる。 リクエスト処理は、開発、本番、サーバーレンダリング、edgeの各エントリーポイントに分かれて存在することが多い。同じ動作を所有するすべての実装を確認し、既知の重複箇所を残さず、同じ変更内で同等性の不足を修正する。 ## ランタイムの境目ではリクエストの情報をはっきり渡し、生成コードは薄くする 分離されたビルド環境やレンダリング環境は、それぞれ独立したモジュールグラフとモジュール状態を持つことがある。ある環境でリクエスト状態を設定すれば別の環境も変わるとは決して想定しない。 - pathname、パラメーター、ヘッダー、cookie、その他のリクエストコンテキストを環境境界越しに明示的に渡す。 - レンダリング直前にコンテキストを初期化し、終了後にクリーンアップする。 - リクエストスコープの状態と小さな公開ファサードを、重い実装から分離する。 - ストリーミングによる消費とクリーンアップのタイミングの両方を検証する。 生成されるエントリーモジュールには、ルート固有のimport、manifest、薄い接続処理だけを含める。リクエストの統括、キャッシュ方針、メソッドのディスパッチ、リダイレクト、エラー処理、ヘッダーのマージ、その他のテスト可能な動作は、通常の型付きモジュールへ移す。 抽出したヘルパーを直接単体テストする。生成コードのテストでは、大きな実装文字列との一致ではなく、委譲と接続処理を検証する。 基盤となるbundler pluginと互換レイヤーの所有境界を明確に保つ。pluginは変換、シリアル化、複数環境ビルド、CSS処理、hydrationの起動を所有できる一方、互換レイヤーはルーティング、リクエストのライフサイクル、レイアウト合成、ナビゲーション、キャッシュ、APIシムを所有する。 ## ファイルパスの書き方をそろえ、複数環境のビルドを最後まで通す モジュールIDや生成importに使うファイルシステムパスには、オペレーティングシステムをまたいで一つの正規セパレーター方針が必要になる。 - ソースコードでは、ネイティブの意味論を保ちながら正規化された識別子を返すパス抽象化を使う。 - processの作業ディレクトリ、解決済みモジュール、実体パス、globの出力、bundlerが報告するIDなど、外部由来の値だけを正規化する。 - 正規パスレイヤーがすでに生成した値を繰り返し正規化しない。 - URLのバックスラッシュ無害化を、ファイルシステムのパス処理と分離する。 - 本当に必要なネイティブパスの例外はすべて文書化し、lintで検査する。 テストではネイティブのパスAPIで入力を構築し、Windows上では実際のバックスラッシュが渡るようにする。期待出力だけを正規化する。すべてをスラッシュだけで構築したクロスプラットフォームテストでは、Windowsの正規化回帰を検出できない。 本番ビルドでは、フレームワークpluginの完全な複数環境builderパイプラインを使わなければならない。汎用の単一環境ビルド呼び出しでは、必要なserver、renderer、clientの各フェーズが通知なく省略されることがある。 ツールチェーンのメジャーバージョンが変わった場合は、現行のtransformer、dependency optimizer、bundler optionの各体系を優先する。旧式のoption名は移行対象として扱う。 仮想モジュールでは、ルート接頭辞付きID、解決済みのnull接頭辞、物理的なモジュール位置が存在しない場合の絶対importも明示的に処理する必要がある。 ## ふだんのリクエストを軽く保ち、キャッシュの動きをはっきり決める 機能固有のランタイムを一般的なリクエスト経路に載せない。 - オプション動作をdynamic importする前に、低コストのリクエストまたはルートmetadataを使って適用可否を判定する。 - ランタイムコードが不要な場合はtype-only importを使う。 - JavaScriptのhandlerが動く前にbundler hookを絞り込み、依存関係、生成ランタイム、prebundle、該当し得ない構文対象を除外する。 - 軽量な状態モジュールと登録モジュールを、キャッシュ、ナビゲーション、レンダリングの実装から分離する。 - 計算済みのキャッシュキーとリクエストから導出した値を再利用する。 - 不変の式とテーブルを外側へ巻き上げる。 - 可能な場合は、決定的な解析とアセット選択をビルド時へ移す。 所有者、無効化、ランタイム間の同等性が明確になるまでキャッシュを追加しない。古いコード、ランタイムごとの条件差、プラットフォームbundleの破損を引き起こすなら、モジュールグラフが小さくても利点にはならない。 ストレージhandlerを単純に保ち、増分再生成の方針はその上位に置く。stale-while-revalidateの動作、single-flightの再生成、再検証metadata、tag無効化をそれぞれ個別に定義する。 期限切れのエントリーは再生成の実行中に古い状態で返してもよいが、明示的に無効化されたエントリーは削除しなければならない。backendの選択を宣言的にし、安全な既定値を維持し、オプションadapterは最初に使う前に登録する。 ## まねするフレームワークと同じ順番で、リクエストを処理する 対象フレームワークのリクエストライフサイクルを、順序付きの契約としてモデル化する。 1. 静的設定のヘッダーは元のリクエストに対して評価される。 2. 静的リダイレクトは元のリクエストに対して評価される。 3. middlewareが実行され、rewrite、redirect、ヘッダー変更を行う場合がある。 4. ファイルシステム処理前のrewriteはmiddleware処理後の状態に対して評価される。 5. 静的ルートとファイルシステムルートを解決する。 6. ファイルシステム処理後のrewriteを実行する。 7. 動的ルートを解決する。 8. fallback rewriteを実行する。 最終レスポンスが同等に見えるという理由だけでステージを移動しない。middlewareがリクエストを変更すると、条件付き照合の結果も変わる場合がある。 既知の同等性不足を隠さず記録する。どの静的アセットがmiddlewareを迂回し、どの公開ファイルがmiddlewareを通るかといった意図的な区別を保つ。 変化するAPIでは、互換性契約が両方を要求する場合に限り、新旧のアクセスパターンをサポートする。たとえば、解決済みフィールドを付加したpromiseなら、awaitを使うパラメーターアクセスと直接のパラメーターアクセスの両方をサポートできる。 公開APIのimportを使うサードパーティーライブラリは、aliasまたは拡張子の正規化で動作する場合がある。ビルドpluginや内部APIに依存するライブラリには明示的な統合が必要であり、インストールしてimportするだけでは不十分である。 ## lockfileを勝手に変えず、チェックに通ったpull requestだけでマージする 無人インストール中にfrozen lockfileを決して迂回しない。依存関係の解決にlockfileの変更が本当に必要な場合は、作業を停止し、その操作をユーザー自身が実行するか許可するよう求める。正確なバージョンとintegrity hashはサプライチェーンの境界である。 保護されたデフォルトブランチへ直接pushしたり、ブランチ保護を迂回したり、管理者権限でmergeを強制したりしてはならない。次の順序を使う。 1. 対象を絞ったfeature branchを作成する。 2. 一貫性のある最小の変更を行い、対象を絞った検査を実行する。 3. リポジトリが要求するメッセージ規則に従ってcommitする。 4. ブランチをpushしてpull requestを作成する。 5. 必須のformat、type、unit、end-to-endのすべての検査が通るまで待つ。 6. 失敗を迂回せず修正する。 7. 承認された方法でmergeし、ブランチを削除する。 リリース自動化にconventional commitからchangelog metadataを導出させる。意図的な遡及再分類に限って文書化された上書き方法を使い、生成されたリリース成果物を手作業で編集しない。 大規模なリファクタリングでは、それぞれが一つのまとまった境界を抽出し、対象を絞ったヘルパーテストを追加し、委譲の検証を更新する、小さなstacked changeを使う。 信頼できないコントリビューションのCIは、secretなし、読み取り専用権限で実行しなければならない。secretを使うpreview workflowでは、認可されたmaintainerが本人確認で保護されたcommandを通じて明示的に起動しない限り、forkをスキップしなければならない。 デプロイされる各exampleには、build matrixへの登録と、期待するroute、status、contentを検証するsmoke testが必要になる。必須検査とデプロイ後の根拠の両方が合格して初めて、リリースまたはデプロイは完了となる。