bundle-size-optimization について
TanStack 関連パッケージの生成 JavaScript バンドルを測定・削減するためのリポジトリスキルです。コーディングエージェントに、シナリオ選択、結果比較、変更の分析と検証を案内し、実行性能と保守性も考慮します。
主な機能
- 01Nx による生成バンドルの測定
- 02ベンチマークスナップショットの保存と比較
- 03ファイル別指標と分析結果の確認
- 04対象を絞った性能ベンチマーク
- 05テストと最終変更の検証
活用シーン
- 01候補を保存済みベースラインと比較
- 02シナリオやファイル別にサイズ変化を特定
- 03最適化による実行速度への影響を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tanstack/ router --skill bundle-size-optimizationインストール後、Claude Code で/bundle-size-optimizationを入力して読み込まれたか確認します。
詳細
バンドルサイズの最適化
概要
ソーステキストではなく、測定したクライアントバンドルを最適化します。信頼できる情報源は、@benchmarks/bundle-size:measure、benchmarks/bundle-size/results/current.json、および benchmarks/bundle-size/dist/ に出力された JS です。
ローカルで反復するには benchmark:bundle-size:run を使用します。同じ測定スクリプトを使い、Nx を通じて選択したパッケージをビルドします。
コマンド
| 用途 | コマンド |
|---|---|
| ベンチマーク全体 | pnpm benchmark:bundle-size:run |
| ベースラインを保存 | pnpm benchmark:bundle-size:run --name baseline --scenario react-router.minimal |
| 測定して比較 | pnpm benchmark:bundle-size:run --baseline baseline --scenario react-router.minimal |
| 候補を保存 | pnpm benchmark:bundle-size:run --name candidate --baseline baseline --scenario react-router.minimal |
| テスト、測定、比較 | pnpm benchmark:bundle-size:run --baseline baseline --scenario react-router.minimal --test-projects @tanstack/router-core,@tanstack/react-router -- tests/path.test.ts tests/link.test.tsx |
| 結果を確認 | pnpm benchmark:bundle-size:query --id react-router.minimal |
| 既存の結果を比較 | pnpm benchmark:bundle-size:diff --baseline ./baseline.json --id react-router.minimal |
| 履歴の差分 | git fetch --quiet origin gh-pages && pnpm benchmark:bundle-size:history --id react-router.minimal --top-deltas 20 |
| ソースの内訳を収集 | pnpm benchmark:bundle-size:run --scenario react-router.minimal --analysis |
| ソースの内訳を確認 | pnpm benchmark:bundle-size:analyze --id react-router.minimal |
| シンボルの参照 | pnpm ts:symbol-references -- --project packages/router-core/tsconfig.json --file packages/router-core/src/utils.ts --symbol last |
名前付き実行では、メトリクスとログが benchmarks/bundle-size/results/runs/<name>/ に保存されます。既存の名前付き結果は上書きできません。
--name を指定しない場合、実行によって results/current.json と、実行した手順のログが置き換えられます。
--baseline には実行名または JSON ファイルのパスを指定できます。--results-dir <dir> を指定すると、現在の実行と名前付き実行の結果ルートが変更されます。
出力されたバンドルは、--dist-dir で別のディレクトリを指定しない限り、引き続き dist/ に保存されます。
名前付きの結果を使う場合は、query、diff、analysis ツールに --current benchmarks/bundle-size/results/runs/<name>/current.json を渡します。
ルール
- Nx コマンドは一度に 1 つだけ実行してください。
- 環境変数、リダイレクト、終了コード、ログ末尾、結果のクエリには、シェルの連結コマンドではなくランナーを使用してください。
- エージェントの実行では、パッケージマネージャーのコマンド表示を省略するため、
pnpm --silent benchmark:bundle-size:run ...を使用してください。 - ランナーは
CI=1とNX_DAEMON=falseを設定します。完全な出力は、current.jsonと同じ場所にあるtests.logとmeasure.logに保存されます。 - ステップが失敗すると、ランナーは停止し、最大 40 行、8,000 文字までのログを表示します。詳細を確認するには、報告されたログを読んでください。
- 標準出力が静かな場合も、Nx がハングしているとは限りません。Nx のリセット/再試行のガードレールを適用する前に、報告されたログで進行状況を確認してください。
- 変更したパッケージには
--test-projectsを使用してください。単体テストのファイルセレクターとフラグは--の後に渡してください。 - ランナーはテストを自動選択しません。型テスト、パフォーマンスベンチマーク、E2E テストは分けて実行してください。
- ベースラインと候補のシナリオ選択、および計測フラグは同一にしてください。保持するスナップショットごとに新しい
--nameを使用してください。 - パッケージのソースを変更した後に
--skip-package-buildsを追加しないでください。デフォルトでは Nx 経由で再ビルドされ、有効なパッケージキャッシュが保持されます。 - まず
gzipBytesを追跡し、initialGzipBytes、rawBytes、brotliBytes、jsFiles、ファイルごとのfilesも確認してください。 - dist パスにはメトリクス ID ではなく
scenarioDir/outDirを使用します。react-router.minimalはdist/react-router-minimal/に対応します。 - 小さな変更では、候補を変更するたびに計測してください。gzip のサイズは、raw バイト数と逆方向に変化することがあります。
- ベースのコミットと比較するには、別のワークツリーで同じシナリオを計測してください。保存した
current.jsonのパスを--baselineに渡します。 - 過去のパターンやベースラインの確認には履歴を使用し、ソースの変更箇所の特定には使用しないでください。履歴はコミット単位のデータです。
- バンドルサイズのために、実行時のパフォーマンスやセキュリティを犠牲にしてはなりません。
- 最初に効果が確認できた時点で止めないでください。計測でのリグレッション、可読性、リスクによって残りの方法が不適切と判断されるまで、妥当なローカル、出力 JavaScript、アルゴリズム上の候補を引き続き試してください。
- ヘルパーをインライン化したり、わかりにくいロジックを単純化したりする場合は、仕組みではなく意味や不変条件を説明する短いコメントを付け、可読性を保ってください。
- ヘルパー/関数をインライン化または削除する前に、TypeScript 言語サービスのスクリプトで参照を確認してください。
pnpm ts:symbol-references -- --project <package>/tsconfig.json --file <decl-file> --symbol <name>を実行します。ヘルパーがほかでも使用されている場合、計測で効果が実証されない限り、バンドルサイズのために一箇所だけをインライン化する価値は通常ありません。参照が残っていない場合はヘルパーを削除し、スクリプトで確認してください。 - 変更対象のパッケージの単体テスト/型テストに加え、
e2e/内の関連する E2E テストを実行してください。 - これ以上の削減でコードの可読性/保守性が損なわれる場合、または削減方法が残っていない場合まで、最適化を続けてください。ユーザー指定のバイト数目標は必須ではありません。
- 上記の実行時、セキュリティ、可読性、保守性、テストの制約を守る場合に限り、大規模でリスクの高いアーキテクチャ上またはアルゴリズム上の変更も検討してください。
- 実行時のセマンティクスが変わる可能性があるため、テストに合格した変更を取り込むべきか判断できない場合は、単体テスト/E2E テストを追加するか、ユーザーに確認してください。コードベースの調査で答えられる場合は、まず調査してください。質問ごとに、推奨する回答も提示してください。
- 実行時パフォーマンスへの影響が不明確な場合は、現実的な入力とエッジケースの入力を使って候補実装を比較する、対象を絞った Vitest ベンチマーク(
*.bench.ts)を追加してください。例:packages/router-core/tests/closing-tag-detection.bench.ts。bench()のケースを実行する前に、各実装が同じ結果を生成することを確認してください。 - 再利用可能なバンドルサイズのパターンを見つけた場合、ツールの不足が判明した場合、または分析能力が不足している場合は、このスキルやベンチマークスクリプトを更新する前にユーザーに確認してください。
ベンチマークのルール
- イテレーション中は、変更したコードが含まれる可能性が最も高いバンドルサイズのシナリオを1つ選びます。router-core と react-router の変更では、デフォルトで
react-router.minimalを使用します。solid-router の変更ではsolid-router.minimal、vue-router の変更ではvue-router.minimal、React Start の変更ではreact-start.minimalまたはreact-start.rsbuild.minimal、Solid Start の変更ではsolid-start.minimalを使用します。 - コードがより包括的なシナリオでのみ取り込まれる場合は、デフォルトの対象シナリオを変更します。たとえば、あるフック/関数が
solid-router.fullでのみ参照される場合は、イテレーションにsolid-router.minimalではなくsolid-router.fullを使用します。 - 変更が複数のパッケージファミリーに影響する可能性がある場合は、迅速にイテレーションするために共有コードをインポートする最小のシナリオを選び、最終決定の前に、次に影響を受ける可能性が高いファミリーをスポットチェックします。
- 最終決定の前に、
--scenarioを指定せずにバンドルサイズのベンチマークをすべて実行し、全シナリオを比較します。対象シナリオが改善していても、外れ値や異常を確認します。 - 公開 API の周辺だけでなく、変更した仕組みを直接ベンチマークします。
- 広範で現実的なシナリオはスモークテスト/回帰テストの対象として維持し、効果の実証には対象を絞ったケースを使用します。
- ベースラインと現在の状態は、同じベンチマークファイルで比較します。実装だけを変える場合は、別のワークツリーを使用します。
- ノイズの多いベンチマークファミリーは、
-t <pattern>を使って個別に実行します。すべてをまとめたスイートでは、極めて短時間の処理に影響が出ることがあります。 - 分岐のある高速パスでは、最良ケース、最悪ケース、想定される混合分布を含めます。
- 単一の呼び出しがタイマーや外れ値のノイズに左右されるほど高速な処理は、1回のベンチマーク反復内でまとめて実行します。
hz、mean、p99/p999、rme、サンプルを合わせて確認します。ノイズの多いhzの値1つだけを信用しないでください。rmeが高い場合や p999 の外れ値が大きい場合は、傾向を示すものとしてのみ扱います。判断する前に、対象をさらに絞ったケースを再実行します。- ケース名はテスト対象の動作にちなんだものにし、今後の読者がどの結果が重要なのか分かるようにします。
- ベンチマークが無効なパスや実行されないパスを測定しないよう、計測ケースの前に正しさを検証します。
アトリビューションラウンド
最適化を最終決定する前に、残すべき正確な本番コードの差分を明らかにします。
- 最適化前のベースラインと候補全体のメトリクスを記録します。
- 本番コードの差分を、論理的な変更箇所または依存する変更箇所のグループに分割します。出力されるコードに影響する可能性がある場合は、構文のみの変更や可読性のみの変更も含めます。
- 独立した変更箇所はそれぞれ、同じベースラインと比較して単独でベンチマークします。変更箇所同士が組み合わせたときにのみ効果がある場合や、相互作用する場合は、関連する組み合わせもベンチマークします。
- 各変更箇所/グループについて、バンドルのメトリクスと、実行時コストが変わる可能性がある場合は対象を絞ったパフォーマンス結果を記録します。
- バンドルサイズまたはパフォーマンスを改善する変更、あるいは正しさ/テスト/スタイルのために必要で、測定結果を悪化させない変更だけを残します。効果がない変更や有害な最適化のみの変更は元に戻します。
- 最終的に組み合わせたバージョンを再ビルドし、再測定します。残した変更が正しさまたはスタイルのために明示的に必要でない限り、アトリビューション前の候補より大きくも遅くもなってはいけません。
最適化の反復
--name baselineを指定してベースラインのシナリオを測定します。- 必要に応じて、差分、出力された JS、ファイルごとのサイズ、分析ソースを確認します。
- 構文ではなくアルゴリズムを分析します。冗長なループ、重複した分岐、繰り返される走査/スライス/小文字化、割り当ての多い処理パス、検索順序、データ形状の選択肢を特定します。
- まず、作業量やコード形状を削減する、動作を維持した最小限のアルゴリズム変更を行います。アルゴリズムの候補を出し尽くしてから、構文のみの変更を試します。
--baseline baselineを指定して再測定し、効果が実証された変更だけを残します。- パッケージの単体テスト/型チェック、関連する e2e、
git diff --checkを実行します。 - アトリビューションラウンドを実施し、最終決定の前に最適化後のカバレッジ/パフォーマンスのワークフローを実行します。
アルゴリズムのパス
ホットファイルでは、コードをフェーズに分け、削除した文字数ではなく削減した処理量に基づいて各フェーズを最適化します。
- 解析/スキャン:ヘルパーによるスキャンと部分文字列の割り当てを組み合わせるより、1回のパスを優先します。可能な場合は、ソース文字列内のオフセットを保持します。
- ツリー/構築:データ形状が共通する場合は、同一のノード作成分岐を統合します。繰り返し使うルート/オプションのフィールドはローカル変数にキャッシュします。
- マッチング/検索:優先順位は維持しますが、スタックへの追加順序が同一のまま保たれる場合に限り、候補のループを統合します。正しさのために必要な場合を除き、サフィックス/プレフィックスのチェックでの割り当ては避けます。
- 抽出/検証:パラメーターは遅延して計算し、必要な場合に限り状態を引き継ぎます。テストで確認できていない限り、スキップされた分岐やパスのない分岐をまたいで部分的なパラメーターを再利用しないでください。
- ソート/スコアリング:計測済みで、かつ読みやすさを維持できる場合に限り、ヘルパー呼び出しや比較関数の連鎖を置き換えます。
- ソート/ツリーの後処理:完全なツリー走査の目的が疎な子配列のソートだけである場合は、構築中に配列の長さが2になった時点でソート対象として記録し、記録した配列を一度だけソートします。
候補ごとに、バンドルの測定前に対象を絞ったパフォーマンスベンチマークを実行します。実行時のリグレッションを隠したり、不変条件の監査を困難にしたりする改善は採用しないでください。
最適化後のカバレッジ/パフォーマンスのワークフロー
最適化が完了したら、次の手順を実行します。
- 5つのサブエージェントを起動し、最適化の差分を既存のテストと照らし合わせてレビューさせます。それぞれに、現在の変更で失敗する可能性のある未追加のユニットテストケース、新たに明らかになったエッジケース、およびリグレッションを見逃す可能性のあるパフォーマンスベンチマークの不足を指摘するよう依頼します。
- リグレッションの可能性が不明瞭な場合は、期待される動作が明確になるまでユーザーに尋ねるか、コードベースを調査します。
- その情報をもとに、対象を絞ったユニットテストとベンチマークを追加します。
- テスト/ベンチマーク/テストスクリプトを支援する変更のみをコミットします。
- 実装の変更をスタッシュします。
- テスト、パフォーマンスベンチマーク、および関連するバンドルサイズの測定を実行し、変更前の結果を
RESULT-optimization-{topic}.mdに記録します。 - 実装の変更をポップします。
- 同じテスト、パフォーマンスベンチマーク、およびバンドルサイズの測定を実行し、変更後の結果を同じファイルに追記します。
- ベンチマーク出力をレビューする際は、標準偏差、誤差の範囲、分散/ノイズ、サンプル数、パーセンタイルを考慮します。結果にノイズが多い場合は、再実行するか、結論の範囲を絞ります。
- 変更前と変更後を比較します。リグレッションがあれば、すべて解消するまで改善を続けるか、リグレッションを元に戻します。
有効な手法:本番環境専用の文字列を削除する、未使用のエクスポートを削除する、ラッパーをフラット化する、一度しか使わないヘルパーをインライン化する、重複するリテラルを避ける、ツリーシェイキングの境界を改善する、動作を維持したまま分岐を簡素化する。
DCE とアノテーション
Rolldown がコードを削除するのは、そのコードが未使用かつ副作用を持たない場合に限られます。プロパティの読み取りはゲッターを呼び出す可能性があり、ストレージやグローバルへのアクセスは観測可能な動作を引き起こしたり、例外をスローしたりすることがあります。
| アノテーション | 有効 | 安全でないケース |
|---|---|---|
/* @__PURE__ */ call() | 未使用の戻り値を削除できる呼び出し/new 式の直前 | 宣言、プロパティの読み取り、セットアップ、ストレージ、DOM/history/リスナーのコード |
/* @__NO_SIDE_EFFECTS__ */ function f() | 関数のすべての呼び出しに副作用がない | グローバル、ストレージ、DOM、history、サブスクリプション、警告、キャッシュに触れる関数 |
sideEffects/モジュールフラグ | 未使用時に、モジュールのインポート時に副作用がない | CSS、ポリフィル、ストレージのハイドレーション、DOM/history のセットアップ |
注意すべき点
- サイズの代理指標として、パッケージの
test:buildを使用すること。 - 測定した
gzipBytesではなく、ソースのバイト数や未圧縮のバイト数を信頼すること。 dist/<scenarioDir>ではなく、dist/<metric-id>を調べること。- バイト数の目標が小さいという理由で、副作用のあるコードに DCE アノテーションを追加すること。
- 変更が「バンドルサイズだけ」を対象としているという理由で、動作テストやベンチマークテストを省略すること。
- ハンク単位の帰属を確認せず、候補全体の結果が改善したという理由だけで変更を残すこと。
- バイト数を削減するために、実行時パフォーマンス、セキュリティ、可読性、保守性を犠牲にすること。
メタ作業
バンドルサイズの測定やワークフロー自体に取り組む必要がある場合は、
benchmarks/bundle-size/README.md
をお読みください。