whyts について
whytsは、TypeScript開発者向けにコンパイラーの性能を測定し、遅いチェックを特定するコマンドラインツールです。認識できるパターンには根拠と対処案を示し、変更前後の実行結果も比較できます。
主な機能
- 01コンパイラー診断とトレースの分析
- 02インポート関係の調査
- 03既知の性能パターンの特定
- 04該当パターンへの対処案
- 05変更前後の測定結果の比較
活用シーン
- 01遅いTypeScriptチェック箇所の特定
- 02再帰的な型インスタンス化エラーの調査
- 03型変更による性能改善の評価
MCP 対応
READMEでは、AIコーディングエージェントに同じ根拠を提供する関連MCPサーバー `whyts-mcp` も紹介されています。`claude mcp add --transport stdio whyts -- npx -y whyts-mcp` でstdio経由で追加できます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
3掲載時のツール一覧 · 1 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| runs | integer | いいえ | 安定したチェック時間を得るためのトレースなし計測の実行回数(デフォルトは 1)。各実行で完全な型チェックを 1 回追加します。中央値を得るには 3 回以上を指定してください。 |
| project | string | はい | tsconfig.json を含むディレクトリ、または tsconfig ファイルへの絶対パス。 |
| typescript | string | いいえ | プロジェクト内のものに代えて使用する typescript パッケージディレクトリへのパス(5.x、6.x、7.x)。 |
| maxOldSpaceMb | integer | いいえ | コンパイラープロセスのヒープ上限(MB)。TypeScript 7 では無視されます。 |
| timeoutSeconds | number | いいえ | 型チェックごとのコンパイラーのタイムアウト秒数。デフォルトは 900。 |
詳細
翻訳後に原文が更新されています。最新の内容は原文をご確認ください。
whyts
TypeScript プロジェクトが遅い理由を突き止めましょう。
コンパイラーの診断、トレース、インポート関係を、具体的な対処に活用できる根拠に変える小さな CLI です。チェックが遅い箇所を示し、よくある 3 つのパターンについては、その理由と対処方法も示します。アカウント、API キー、LLM は必要ありません。
npx whyts
tsconfig.json があるフォルダーで実行してください。実際のコンパイラーを 1 回実行し、対処方法を最大 3 つ表示します。型の変更で改善したことを証明するには、npx whyts compare を使ってください(下記参照)。同じ根拠を AI コーディングエージェントに渡すには、claude mcp add --transport stdio whyts -- npx -y whyts-mcp を使ってください。
whyts で見つかった事例:型チェックが 7.4 s から 0.06 s に短縮(TanStack Form)、vue-tsc が 80 s から 0.56 s に短縮(Vue)。実際の事例をご覧ください。
試してみる
Node.js 20 以降と、依存関係をインストール済みの TypeScript プロジェクトが必要です。
npx whyts --project .
繰り返し実行する場合は、バージョンを固定してください:npx [email protected] --project .
チェックアウトから実行するには:
git clone https://github.com/musatoktas/whyts.git
cd whyts
npm ci --ignore-scripts
npm run demo
node src/cli.js --project /path/to/your/tsconfig.json
診断と対処方法
デフォルトでは、whyts は最大3件のアクションと1行の概要を出力します。各アクションには、パターン、場所、簡単な対処方法、リンクが含まれます。上の画像は実行時の記録です。完全なレポートを表示するには、--verbose を使用してください。
whyts 0.8.2 · TypeScript 5.9.3 · repro-project/tsconfig.json
1. [measured] Recursive type instantiation: DeepKeys<Values>
Where: k.ts:4:17
TS2589 at this place: Type instantiation is excessively deep and possibly infinite.
It hit the instantiation limit inside DeepKeysAndValuesImpl (../src/util-types.ts:151).
DeepKeysAndValuesImpl refers to itself: DeepKeysAndValuesImpl -> DeepKeyAndValueArray -> DeepKeysAndValuesImpl.
Type argument JsonData (k.ts:2): JsonData refers to itself.
Fix: Stop the expansion of DeepKeysAndValuesImpl when it meets a type it already visited. Or add a depth limit to it. Check that the fix keeps the same results.
Cost: 13118 ms, at most 99.5% of Check time (upper bound, not a saving)
Docs: https://github.com/microsoft/TypeScript/wiki/Performance
Check 13.18s · Total 13.55s · 65 files · 1 compiler error · 1 finding in the full report. Use --verbose to see them.
3つのパターンに対処方法があります。それぞれ、追加される前に実際のプロジェクトで確認されたものです。
| パターン | トレースの根拠 | 対処方法 |
|---|---|---|
recursive-type-instantiation | TS2589 エラーと instantiateType_DepthLimit イベントです。whyts は、上限に達した型パラメーターを持つ型エイリアス、そのエイリアス間の名前による参照サイクル、および自身を参照する型引数を特定します | 訪問済みの型で展開を止めるか、深さの上限を追加してください |
variance-computation | 型 ID、型パラメーターの数、結果を含む getVariancesWorker イベントです。whyts は最外部のイベント、最大のネストされたイベント、およびそれらの合計時間を報告します | 型パラメーターを減らし、戻り値の型を簡素化してください。in または out の注釈で必ず解決するとは限りません |
program-load | プログラムは大きく、チェック時間は長くありません。コンパイラーのカウンター(合計時間、チェック時間、使用メモリ、解析およびバインド時間)とインポートグラフです。whyts は node_modules 内のファイル、最大のパッケージ、それらの大部分を取り込むファイル、およびそれらを取り込む typeof import() の参照を報告します | より範囲の狭いエントリーポイントを使うか、パッケージのメンテナーに、参照をオプトイン方式のエントリーポイントに移動するよう依頼してください。skipLibCheck は役に立ちません。チェックを省略するだけで、読み込みは省略しないためです |
program-load アクションは次のようになります(vuetify 4.2.4 の例です。import { createVuetify } from "vuetify" だけがあるプロジェクトです。行は短縮されています)。
1. [measured] Program load: 421 dependency files, vuetify has 407; Check time is 0.01s of 4.22s
Where: ../node_modules/vuetify/lib/framework.d.ts:1:1
The program has 480 files: 421 in node_modules (13.8 MB of source), 58 compiler library files and 1 of your own. ... Memory used 577 MB in the traced run.
Largest packages by files: vuetify 407 (12 MB), @vuetify/v0 3 (0 MB), vue 2 (0 MB), ...
409 of them (11.9 MB) come only through 218 typeof import() type references in ../node_modules/vuetify/lib/framework.d.ts, to ../node_modules/vuetify/lib/components/index.d.ts (192), ...
Shortest import chain: index.ts -> ../node_modules/vuetify/lib/framework.d.ts -> ../node_modules/vuetify/lib/components/index.d.ts
Fix: Each typeof import() reference in ... loads its target for every project that imports the package. Ask the maintainers to move them to an opt-in entry point. skipLibCheck skips checking these files, not loading them. ...
ルール:
- 診断がコンパイラーによって記録されている場合(エラーまたはトレースイベント)、診断は
measuredです。型エイリアスを名前でたどる処理は構文のみを対象とするため、名前で照合できないインポートを経由するサイクルや、プログラム内の宣言ではない型を経由するサイクルを検出できない場合があります。 - 分散パターンには合計100 ms以上の合計時間が必要で、50 ms以上のイベントのみが対象です。プロジェクト内で宣言された型のみが一覧表示されます。依存関係の型はプロジェクト側で変更できないため、対処方法は示されません。
- コストは、その場所で記録された最大の間隔であり、チェック時間の最大N%として表示されます。トレース中の上限値であり、節約できる時間の予測ではありません。
- コンパクトレポートでは、既知のパターンがない遅いチェックは、チェック時間の3%を超える場合にのみ表示されます。型名によってすでに診断で説明されているチェックは、重複して一覧表示されません。
- トレースイベント(
instantiateType_DepthLimit、getVariancesWorker)は TypeScript 5.9、6.0、7.0 に存在します。TypeScript 7 ではcheckerIdが付与されます。whyts はチェッカーを1つ使用するため、型 ID は一意のままです。 program-loadパターンには、次の条件がすべて必要です。node_modules内のファイルが200個以上、合計時間が1秒以上、チェック時間が1秒以下かつ合計時間の10%以下、さらに単独で100個以上(依存関係ファイルの25%)のファイルを取り込むファイルが1つあることです。コンパイラーライブラリのファイルと独自のファイルは、依存関係ファイルとして数えません。通常量の独自コードがあるプロジェクトはチェック時間が長くなるため、このパターンには該当しません。カウンターはトレース実行時の値であり、使用メモリは通常実行時より大きくなります。program-loadはチェックではなく、プログラムの測定結果です。ファイルを削除すればチェックが速くなるという意味ではありません。このようなプログラムのチェック時間は、すでに短いためです。宣言ファイル内のtypeof import('pkg')をインポートとして読み取ります。通常のインポートでも取り込まれるファイルは、それらの参照だけによって取り込まれたものとしては数えません。- 対処方法は簡潔な指示であり、進むべき方向を示します。whyts は、プロジェクト上で対処方法を測定していません。
実例
以下の各ケースでは、whyts が問題箇所を見つけました。修正は各プロジェクトでプルリクエストとして公開されているため、メンテナーがマージするまでは提案段階です。Vuetify の行は、修正のない Issue での測定結果です。各数値は、記載したコンパイラーバージョンで私自身が測定したものです。
| プロジェクト | whyts が見つけた内容 | 結果 |
|---|---|---|
| tRPC (#6997) | Serialize(serialize.ts:35)で、Prisma.JsonValue などの再帰的な JSON 型を含む型に対して TS2589 が発生 | PR #7660 で修正。再現ケースでは main に TS2589 が発生していましたが、修正後はエラーがなくなりました。インスタンス化の回数は 89,700 から 78,502 になりました。packages/server の型チェック時間は変わりませんでした(誤差の範囲内) |
| TanStack Form (#1474) | DeepKeys で TS2589 が発生:型エイリアスが DeepKeyAndValueArray を介して自身を参照 | PR #2422 で修正。再現ケースのチェック時間は 7.4 秒から 0.06 秒になりました(whyts compare、分けて実行した 10 回の測定)。form-core は誤差の範囲内にとどまりました |
| TypeORM (#8559) | QueryPartialEntity.ts:17 の QueryDeepPartialEntity で、自身を含む型に対して TS2589 が発生 | PR #12943 で修正。whyts は変更前にこの診断を報告し、変更後は報告しません |
| Vue (#14323) | void で終わる defineEmits の呼び出しシグネチャと defineModel を組み合わせると、OverloadUnionRecursive(@vue/shared)が深さ制限まで実行されます。チェック時間の 100% を占めています | PR #15851 で修正。emit が 1、2、3 個の場合の vue-tsc の実行時間は、31.1、55.3、80.0 秒から 0.42、0.49、0.56 秒になりました(TypeScript 5.9.3)。オーバーロードが 40 個あるコンポーネントで model を使用すると、以前と同様に TS2589 が発生します |
| Hono (#2399) | 再帰的な JSON 型を使うと、JSONParsed(src/utils/types.ts:53)で TS2589 が発生 | 訪問済みの型で処理を止める修正がIssue #5543 にあります。深さ制限を設けると .d.ts の出力は 313,638 バイトになりましたが、修正後は 762 バイトに抑えられます |
| node-redis (#2975) | 2 つの RedisClusterType インスタンス化の構造的な比較 1 回に、4.3 秒のチェック時間のうち 4.1 秒を費やしています | 4.7.0 から 6.3.0 までの測定結果はコメントにあります |
| Vuetify (#23005) | import { createVuetify } from "vuetify" だけで、480 ファイルのプログラムが生成されます(4.2.4 での tsc:使用メモリー 535,557K、チェック時間 0.01 秒、合計時間 2.56 秒)。program-load は lib/framework.d.ts を示しています。そこにある 218 個の typeof import() 参照(GlobalComponents ブロックを含む)が、421 個の依存ファイルのうち 409 個を取り込んでいます | Issue はオープンです。ここでは修正を提案していません。GlobalComponents ブロックだけを空にすると、ファイル数は 111、使用メモリーは 101,701K になりました。これは上限を把握するための測定であり、修正ではありません。グローバルコンポーネント型が削除されるためです。4.2.4 と TypeScript 5.9.3 で測定 |
検出内容
| 検出項目 | 根拠 | 主張しないこと |
|---|---|---|
| ソースチェックチェーン | 同一スレッド内の時間的なネストに基づいてグループ化した、関連する式/変数の位置、包含関係にあるチェーンの所要時間、名前と宣言位置を含む型の比較 | ネストや包含関係にある比較が、低速化の原因を証明すること |
| 広範な対象範囲 | 有効な include と設定されたルートの数 | 広範な対象に含まれるファイルがすべて不要であること |
| レビューする価値のあるルート | インポーターが観測されなかった生成ルートと出力ルート。テストルートと spec ルートは数のみ | インポートされていないファイルを安全に削除できること |
| 重複する型バージョン | プログラムに読み込まれた @types パッケージの複数のバージョン | 重複するバージョンをすべて安全に揃えられること |
| バレルの到達範囲 | 再エクスポート数、インポーター、推移的なファイル到達範囲、到達したファイルのうち設定済みのルートであるもの | 排他的なチェックコスト、バンドラーの動作、または削減効果の保証 |
| チェックに時間がかかるファイル | 記録された checkSourceFile のトレース区間 | 合計時間の完全な内訳、または正確にどの型に時間がかかっているか |
| コンパイラーエラー | 最初の5件のエラー、エラーコードごとの件数、未解決のモジュールが多数を占める場合の警告 | 依存関係が欠落している場合に、計測時間が有効であること |
| 再帰的な型のインスタンス化 | TS2589 の位置、インスタンス化上限に達した型エイリアス、名前に基づく参照サイクル、自己参照する型引数 | 一覧にあるサイクルが唯一の原因であること |
| 分散の計算 | プロジェクトの型に対する最外層の getVariancesWorker イベント、ネストされた型、ユニオンの所要時間 | 分散注釈によってコストがなくなること |
検出項目には 測定済み、観測済み、または レビュー のラベルが付いています。測定済みのソースチェックチェーンが最初に表示され、プロジェクトコードが依存関係より優先されます。10 ms 未満のチェーン区間と100 ms 未満のファイル区間は、記録データには残りますが、検出項目にはなりません。構造に関する提案は、独立したセクションに続きます。提案された変更には、新たな計測と動作チェックが必要です。whyts が予測される速度向上をでっち上げることはありません。
ファイルから式とその型へ
npm run demo:types
16 行の例では、676 個のルートを持つマップ済み API クライアントを、パブリッククライアントの型に代入しています。レポートには、client.ts:16:14 での代入、そのチェック内で記録された FullClient と PublicClient の比較、そして 8 行目と 11 行目にあるそれぞれの宣言が表示されます。たとえば、あるローカルの TypeScript 5.9.3 の実行では、変数チェックに 38.4 ms、この比較に 31.8 ms が記録されました。これらはその実行における包括的なサンプルであり、安定したベンチマークや高速化の主張ではありません。実行が速い場合、サンプリングされた式/比較イベントが省略されることがあります。
同じファイルとトレーススレッド内のネストされたチェックは、1 つのチェーンとして表示されるため、ほかのコストの高いチェックを表示する余地が確保されます。各チェーンには、開始位置と、関連するチェックが最大 5 つ含まれます。注目対象は、チェーンの記録された所要時間の 80% 以上を占める、記録された最も深い非識別子チェックです。そのようなチェックがない場合は、チェーン内で最大のチェックが選ばれます。これは移動のためのヒューリスティックです。遅延されたチェックは、それより前のソース行を訪れることがあり、時間的なネストは AST の包含関係でも因果関係の証明でもありません。各要素の包括的な所要時間は重複するため、合計してはいけません。
型 ID は、128 MiB を超えるファイルも含め、コンパイラーの types.json から選択的に解決されます。whyts は個々の記述子をストリーミングし、選択されたレポート項目に必要な ID だけを保持します。名前付き型、コンパイラーフラグ、利用可能な場合はユニオン/インターセクションのメンバー数、宣言位置を確認すると、具体的な調査箇所がわかります。プロジェクトのコンパイラースキャナーは、宣言の最初のトークンを表示するため、先頭の空白とコメントを読み飛ばします。解決できなかった ID は明示的に残ります。包含する記録済みの式がない比較は、ソース位置を示さずに別途一覧表示されます。定義位置が依存関係内を指す場合もありますが、これはコンテキストを示すものであり、修正案ではありません。
プロジェクトファイルの区間は独自の一覧に表示されるため、依存関係の大きなチェックによってすべてのプロジェクトファイルが隠れることはありません。バレルを経由して到達するすべてのファイルがすでに設定済みのルートである場合、whyts は、そのインポートだけを変更してもプログラムからそれらのファイルを除外できない理由を説明します。
Check 時間に占める割合
測定された各チェーンとファイル区間には、上限値が表示されます。ターミナルのレポートには「Check 時間の最大 N%」と表示されます。JSON では checkTimeShareUpperBoundPercent です。
この値は、包括的な区間をコンパイラーの Check time で割ったものです。予測値ではありません。コンパイラーはトレース実行中に区間を記録しており、トレース実行は通常の実行より遅くなります。コードを変更しても、この割合全体がなくなるわけではありません。
ファイルの説明
node src/cli.js explain src/shared/value.ts --project /path/to/project
ファイルパスは、選択した tsconfig ディレクトリからの相対パスです。結果には、設定済みのルートである理由、または観測された最短のインポート/参照チェーンのいずれかと、直接のインポーターが表示されます。インポートの解決では、TypeScript の paths、再エクスポート、動的インポート、ESM/CJS の使用モードが考慮されます。
これは簡潔な説明であり、tsc --explainFiles の完全な実装ではありません。ライブラリの取り込み、アンビエント型ディレクティブ、シンボリックリンク、プロジェクト参照のリダイレクトについては、コンパイラー独自の説明が必要になる場合があります。
JSON と自動化
node src/cli.js --project apps/web/tsconfig.json --json > report.json
node src/cli.js --project apps/web --timeout 300 --no-color
バージョン 0.7 では、既存のフィールドを変更せず、追加フィールド diagnoses を追加します。各エントリーには、pattern、title、confidence(measured)、location、milliseconds、checkTimeShareUpperBoundPercent、evidence、remedy、docs が含まれます。evidence には、event、typeName、location、時間、およびパターンのフィールドが含まれます。compilerErrors には、TS2589 の発生箇所を含む新しいリスト excessiveDepth が含まれます。--verbose オプションが変更するのは、ターミナル出力のみです。JSON は常に完全な内容を出力します。
JSON には、schemaVersion、コンパイラのバージョン、概要、単位付きの診断、エビデンス付きの検出結果、ホットスポット、警告が含まれます。バージョン 0.3 では、スキーマバージョン 1 と、既存の hotspots、projectHotspots、sourceHotspots、typeHotspots の意味を維持したまま、sourceGroups と typeDescriptors が追加されます。測定されたソースの検出結果では、ルール source-check-chain とチェーンのエビデンスが使用されるようになりました。sourceGroups には、root、members、memberCount、包含時間の milliseconds、選択された式の focusMilliseconds が含まれます。typeDescriptors には、ファイル/スキャン済み/保持バイト数と、要求済み/解決済み ID 数が記録されます。ソースと型のレコードには、ミリ秒単位の所要時間が含まれます。ソースおよび宣言の位置情報では、1 始まりの line/character を使用し、ソースの pos/end はコンパイラの UTF-16 オフセットです。進捗情報は stderr に出力されます。終了コード:
| コード | 意味 |
|---|---|
0 | 分析が完了し、型チェックに成功しました |
1 | ツール/設定のエラー、未対応のコンパイラ、またはタイムアウト |
2 | レポートは利用できますが、コンパイラがエラーを報告しました |
検出結果だけで CI が失敗することはありません。このリリースは診断を行いますが、パフォーマンス予算の適用は行いません。
ネイティブ TypeScript 7 の実行では、JSON に 4 つのフィールドが追加されます。compiler(native)、experimental(true)、graphTypescriptVersion、checkers(1)です。typescriptVersion はネイティブコンパイラのバージョンです。
繰り返し測定する
1 回の実行だけでは、十分な根拠になりません。私たちのテストでは、同じツリーでも実行ごとに Check time が最大 10%変化しました。複数回測定するには --runs を使用してください。
npx whyts --project . --runs 5
仕組み:
- whyts は最初に、通常のトレース付き実行を行います。チェーンとファイルに関するすべての結果は、この実行から得られます。
- 次に whyts は、トレースなしでさらに N 回実行します。トレースはコンパイラを遅くし、大きな型ダンプを追加するためです。
- 各実行では、新しい一時的なインクリメンタルキャッシュを使用します。各実行でプログラム全体をチェックします。
- フラグは、
--generateTraceを除き、トレース付き実行と同じです。独自のビルドキャッシュは変更されません。 - トレース付き実行では、すでにすべてのファイルが読み込まれています。N 回の実行では、ファイルキャッシュがウォーム状態です。このモードでは、whyts はウォームアップ実行を追加しません。
- レポートには、中央値、最小値、最大値、ばらつきが表示されます。ばらつきは、(最大値から最小値を引いた値)を中央値で割り、パーセントで表したものです。
- デフォルトは
--runs 1です。実行回数が 1 の場合、追加の実行は行われず、timingsフィールドもありません。
レポートの最初の行にある Check time と Total time は、トレース付き実行の値です。実行前後の比較には、トレースなしの値を使用してください。
JSON には追加フィールド timings が含まれます。スキーマバージョン 1 はそのままです。このフィールドには、mode、runs、checkers、flags、compilerFiles、compilerExitCodes、checkTime、totalTime が含まれます。各時間オブジェクトには、unit(s)、runs、values、median、min、max、spreadPercent が含まれます。コンパイラが出力する値は丸められています。JavaScript コンパイラは 10 ms 刻みで出力します。
TypeScript 7:トレースなしの実行では、トレース付き実行と同様に --checkers 1 を使用します。tRPC の packages/server プロジェクトの Check time を、各モードで交互に 15 回ずつ測定しました。ばらつきは、チェッカー 1 個で 18.1%、デフォルトのチェッカー数で 19.2%でした。デフォルトモードで安定性が向上することは確認できませんでした。チェッカーを 1 個にすると、フラグがトレース付き実行と同じになるため、チェーン時間と Check time は同じ処理を表します。この時間を、デフォルトの並列 tsc 実行と比較しないでください。このリリースでは、複数のチェッカーはサポートされていません。
2つのプロジェクトまたは2つのレポートを比較する
ライブ比較では、このマシンで両方のプロジェクトを実行します。
npx whyts compare --baseline ./before --candidate ./after --runs 10
各側にはディレクトリまたは tsconfig ファイルを指定します。--runs は各側の計測実行回数です。デフォルトは 5 で、範囲は 1~100 です。--timeout、--max-old-space-size、--typescript、--json のオプションも使用できます。共通の --typescript を指定すると、両側で同じコンパイラーを使用します。
オフライン比較では、2つの JSON レポートを読み込みます。
npx whyts --project ./before --runs 10 --json > before.json
npx whyts --project ./after --runs 10 --json > after.json
npx whyts compare before.json after.json
実行順序とウォームアップ
- まず各側を1回ずつ実行します。ベースライン、次に候補の順です。これらのウォームアップ実行は JSON の
warmupに含まれ、数値には含まれません。 - 最初の実行では、すべてのファイルをディスクから読み込みます。先に実行する側だけがこのコストを負担することになります。テストでは、ファイルキャッシュが空の状態での最初の実行は、その後の実行より Total time が 10%~15%長くなりました。Check time に変化はありませんでした。
- 計測実行はペアで続き、ペア内の順序は毎回入れ替わります。A B、B A、A B、B A の順です。A がベースラインです。
- この順序により、マシンの負荷が徐々に変化しても、どちらか一方が有利になることはありません。N が奇数の場合は、ベースラインが候補より1回多く先に実行されます。
- すべての実行で、
--runsのトレースなしモードを使用します。JSON のorderに実行順序が表示されます。
ノイズに関するルール
このルールは単純であり、統計的検定ではありません。
- 各側について、計測値の最小値と最大値を求めます。
- 2つの範囲が重なる場合、結果は
within-noiseです。範囲が端で接する場合も、重なるものとします。 - 2つの範囲が重ならない場合、結果は
separatedです。候補はfasterまたはslowerになります。 - 各側で少なくとも3回の計測実行が必要です。それより少ない場合、結果は
insufficient-runsです。
中央値の差はミリ秒単位と、ベースラインの中央値に対する割合で示されます。このルールでは、信頼区間も効果量も示されません。
両側の分布が同じで、各実行が独立している場合、範囲が重ならない確率は 2 を C(2N, N) で割った値です。N = 3 では 10%、N = 5 では 0.79%、N = 10 では 0.0011% です。JSON では rule.chanceWithoutDifference に分数で表示されます。負荷の急増や変動があると、この前提は成り立ちません。少なくとも5回実行してください。比較中は、マシン上でほかの負荷の高い作業を停止してください。
このルールは保守的です。小さな速度低下でも within-noise になることがあります。実行回数を増やすと範囲が広がるため、回数を増やせば常に有効というわけではありません。テストでは、Check time が約 10%増加しても within-noise となる場合があり、約 30%増加した場合は常に separated となりました。数値については VALIDATION.md を参照してください。結果が separated でないからといって、差がないことの証明にはなりません。
比較前のチェック
次の場合、whyts は終了コード 1 で停止します。
- 一方がネイティブコンパイラーを使用し、もう一方が使用していない
- チェッカーの数が異なる
- 計測実行のフラグが異なる
- 一方のレポートには
timingsがあり、もう一方にはない - レポートのスキーマバージョンが不明、またはファイルが whyts のレポートではない
次の場合、whyts は警告を出して処理を続行します。
- TypeScript のバージョンが異なる
- 2つのプログラムのファイル数が異なる(コンパイラーの
Files行) - ライブ比較で、2つの tsconfig ファイルのコンパイラーオプションが異なる
- 両側で同じコンパイラーエラーが報告される(警告は
COMPILER ERRORSで始まります) - エラーの大半が未解決モジュールである
- 2つのレポートの whyts のバージョンが異なる
コンパイラーエラーと比較可能な側
コンパイラーは、たとえば TS2589 などのエラーで早期に停止することがあります。その場合、コンパイラーの処理量が減り、所要時間も短くなります。処理内容が異なる2つの側の時間を比較しても、有効な比較にはなりません。
whyts は、次の場合に側を比較不能とします。
- 一方の側にはコンパイラーエラーがあり、もう一方にはない場合
- 両方の側にエラーがあり、エラー数またはエラーコードが異なる場合
側が比較不能の場合、whyts は NOT COMPARABLE とその理由を表示します。各側の実測時間とエラーの概要も表示します。差分と判定は表示しません。
両方の側に同じエラーがある場合、whyts は判定を維持し、COMPILER ERRORS の警告を表示します。エラーデータのないレポートでは、コードの比較は行われません。エラーのない側が判定を決定します。
オフライン比較でのチェーン
whyts は、ソースチェックのチェーンをファイル、行、文字位置で、ファイルチェックの区間をファイルで照合します。チェーンより上で編集すると、その行が移動します。その場合、チェーンは元の行では消え、新しい行では新しく表示されます。結果には、各エントリーが新規、消失、増加、減少、変更なしのいずれかで表示されます。エントリーは、10 ms 以上かつ 10% 以上変化した場合に増加または減少したと判定されます。これらは表示上のしきい値であり、統計値ではありません。
これらの値は、各レポートにつき1回のトレース実行から取得します。これらはサンプルです。一方のレポートにしかないチェーンは、もう一方のレポートでは 10 ms のしきい値を下回っているか、上位5つのチェーンに含まれていない場合があります。もう一方のレポートにそのチェーンが記録されていれば、whyts は比較します。もう一方のレポートに記録されていなければ、そのチェーンを新規または消失として表示します。
出力と終了コード
--json は比較結果を出力します。出力には kind(comparison)、mode、baseline、candidate、checkTime、totalTime、warnings が含まれます。ライブ比較では、runs、order、warmup、measuredRuns も追加されます。オフライン比較では、findings が追加されます。進行状況は stderr に出力されます。
バージョン 0.7.1 では3つのフィールドが追加されます。スキーマバージョン 1 は変わりません。comparable は true または false です。reason は null、または側が比較不能である理由を示すテキストです。baseline.compilerErrors と candidate.compilerErrors には count と codes が含まれ、レポートにエラーデータがない場合は null になります。comparable が false の場合、checkTime と totalTime の verdict は not-comparable になります。差分フィールドは null です。時間の概要はそのまま表示されます。
| コード | 意味 |
|---|---|
0 | 比較が完了し、側は比較可能です。両方の側に同じコンパイラーエラーがある場合は、目に見える警告が表示されますが、別のコードにはなりません。 |
1 | 失敗です。不正な引数、読み取れないファイル、コンパイラーの失敗、タイムアウト、比較の拒否が含まれます。また、側が比較不能な場合も該当します。 |
側が比較不能の場合、実行に有効な結果がないため、終了コードは 1 です。終了コードだけを確認するスクリプトは、これを成功と解釈してはいけません。出力自体は完全です。このケースと失敗を区別するには、JSON の comparable を使用してください。失敗時は stderr に出力され、JSON は出力されません。
AI コーディングエージェントでの使用
AI コーディングエージェントは、コードを読むだけでは型チェックが遅い箇所を見つけられません。生のトレースは、エージェントのコンテキストに収めるには大きすぎます。whyts には MCP サーバー(Model Context Protocol、stdio 経由)があります。エージェントには、計測済みの JSON を数 KB だけ渡します。
npx -y whyts-mcp で起動します。パッケージ whyts-mcp は whyts と MCP パッケージに依存しているため、npm install whyts ではそれらはインストールされません。以下のツールを利用できます。
| ツール | 用途 | 時間 |
|---|---|---|
analyze | チェック時間がどこにかかっているかを見つけます。チェック時間、コンパイラーエラーの概要、対処法を含む diagnoses、計測結果の上位 5 件、警告を返します。 | トレース付きの型チェック 1 回と、トレースなしのチェック runs 回 |
compare | 変更によってチェックが速くなったか遅くなったかを検証します。中央値と理由とともに、separated、within-noise、not-comparable のいずれかを返します。 | 約 2 x runs + 型チェック 2 回(runs の既定値は 5 です) |
explain | あるファイルがプログラムに含まれる理由を、インポートチェーンと直接のインポーターとともに示します。 | 数秒。型チェッカーは実行しません。 |
各結果は 1 つの JSON テキストで、最大 12,000 文字です。whyts が結果を短縮した場合、フィールド truncated に削除された内容が示されます。パスは絶対パスです。エラー時は、kind と hint を含む isError が返されます。型チェックを同時に 2 回実行すると計測時間が歪むため、whyts は一度に 1 件だけ呼び出しを実行します。クライアントからプログレストークンが送信されると、whyts は 15 秒ごと、および各計測実行時にプログレス通知を送信します。
compare の結果の例(runs は 5):
{"tool":"whyts","mode":"compare","decision":"separated","direction":"faster",
"rationale":"Every run of the candidate was faster than every baseline run (5 runs per side; the min-max ranges do not overlap). ...",
"checkTime":{"baseline":{"median":0.21,"min":0.2,"max":0.22},"candidate":{"median":0.03,"min":0.03,"max":0.04},"deltaMilliseconds":-180,"deltaPercent":-85.7}}
クライアントのセットアップ
Claude Code:
claude mcp add --transport stdio whyts -- npx -y whyts-mcp
チーム向けにエントリを .mcp.json に書き込むには、--scope project を追加します。Claude Code は、エントリの timeout フィールド(ミリ秒)で各ツール呼び出しを制限します。プログレス通知によって、この制限が延長されることはありません。
Cursor の場合は、.cursor/mcp.json(プロジェクト)または ~/.cursor/mcp.json(すべてのプロジェクト)に記述します。
{
"mcpServers": {
"whyts": { "command": "npx", "args": ["-y", "whyts-mcp"] }
}
}
GitHub Copilot を使用する VS Code の場合は、.vscode/mcp.json に記述します。
{
"servers": {
"whyts": { "command": "npx", "args": ["-y", "whyts-mcp"] }
}
}
Codex:
codex mcp add whyts -- npx -y whyts-mcp
Codex は 60 秒後にツール呼び出しを停止し、サーバーの起動は 10 秒後に停止します。~/.codex/config.toml で両方の制限を引き上げます。
[mcp_servers.whyts]
command = "npx"
args = ["-y", "whyts-mcp"]
startup_timeout_sec = 60
tool_timeout_sec = 900
その他のクライアントでは、コマンド npx -y whyts-mcp を stdio 経由で実行します。このサーバーは、2025-11-25 プロトコルと 2026-07-28 プロトコルに対応しています。
このサーバーは、MCP Registry に io.github.musatoktas/whyts として公開されています。
エージェントには、docs/agents.md にある簡潔なルールを渡してください。
このサーバーは、tsc と同様に、指定したプロジェクトの TypeScript コンパイラーを実行します。プロジェクトは編集しません。typescript 入力には実行するコンパラーパッケージを指定するため、エージェントには信頼できるパスだけを渡してください。
MCP パッケージ(@modelcontextprotocol/server と zod)は whyts の依存関係ではありません。これまでどおり、npm install whyts でインストールされるのは typescript のみです。パッケージ whyts-mcp は whyts と同じバージョンで、その正確なバージョンに依存します。コマンド whyts mcp も引き続き動作します。MCP パッケージが見つからない場合は、npx -y whyts-mcp が表示されます。
代替方法として、whyts パッケージからサーバーを起動する場合は、npx -y -p [email protected] -p @modelcontextprotocol/server@2 -p zod@4 whyts mcp を実行します。
大規模プロジェクト向けのオプション
| オプション | 意味 |
|---|---|
--verbose | レポート全体を出力します。デフォルトでは、アクションを最大3件と要約行を1行出力します。 |
--timeout <seconds> | コンパイラの制限時間です。デフォルトは900です。 |
--runs <N> | トレース実行の後に、トレースなしでN回実行します。レポートとJSONには、中央値、最小値、最大値が表示されます。デフォルトは1です。「繰り返し測定する」を参照してください。 |
--max-old-space-size <MB> | コンパイラプロセスのヒープ上限を設定します。範囲は256~1048576です。TypeScript 7では無視されます。 |
--typescript <path> | プロジェクトのコンパイラの代わりに、このTypeScriptパッケージディレクトリ、またはそのlib/typescript.jsを使用します。バージョンは5.x、6.x、または7.xです。 |
node src/cli.js --project packages/compiler --max-old-space-size 10240
node src/cli.js --project . --typescript node_modules/typescript
トレースでは、チェックの後に型ダンプを書き出します。コンパイラはダンプをTotal timeに含めません。drizzle-ormのtype-testsプロジェクトでは、Total timeは13.58秒、Dump types timeは249.3秒でした。コンパイラプロセスには263.5秒かかりました。レポートには、実時間とダンプ時間が別々に表示されます。
デフォルトのタイムアウトは900秒です。これは、0.4のテストにおけるコンパイラプロセスの最長時間(263.5秒)の3倍を超えます。コンパイラの実行中は、30秒ごとに進捗メッセージがstderrに出力されます。
シグナルによってコンパイラが停止した場合、エラーにはコンパイラのstderrの末尾の行が表示されます。ヒープエラー、SIGABRT、SIGSEGV、またはSIGKILLの場合、エラーには--max-old-space-sizeを使用するよう案内が表示されます。オペレーティングシステムのメモリ不足キラーやネイティブクラッシュによっても、SIGKILLまたはSIGABRTが発生することがあります。
TypeSpecコンパイラプロジェクトは、デフォルトのヒープ設定では停止しました。--max-old-space-size 10240を指定すると完了しました。
コンパイラエラー
レポートには、ファイル、行、コードとともに最初の5件のエラーが表示されます。また、各エラーコードの件数も表示されます。
エラーの半数以上が、モジュールまたは宣言を解決できないエラー(TS2307、TS2792、TS2688、TS7016、TS6053)である場合、依存関係が不足しているか、ビルドされていない可能性があります。その場合、計測時間が正しくないことがあります。レポートには警告が表示されます。
例:TanStack Routerリポジトリのreact-routerでは、ビルド前に1887件のエラーが発生し、チェック時間は4.76秒でした。ビルド後はエラーが0件になり、チェック時間は12.43秒でした。
JSONバージョン0.4では、次のフィールドが追加されます。スキーマバージョン1と既存のすべてのフィールドはそのままです。
compilerErrors:total、first、codes、missingDependencyErrors、measurementMayBeInvalid。summary.dumpTypesSeconds。- ファイル区間、ソースチェック、チェーンの
checkTimeShareUpperBoundPercent。 review-root-filesのevidence.testRootsExcluded。
summary.errorCountは、解析されたコンパイラエラー行の数をカウントするようになりました。
TypeScript 7(実験的)
whyts は、ネイティブの TypeScript 7 コンパイラーでプロジェクトをプロファイルできます。このサポートは実験的です。
使用するには、プロジェクトに typescript@7 をインストールするか、--typescript でパッケージのディレクトリを指定します。その後、通常どおり whyts を実行します。
npx whyts --project . --typescript node_modules/typescript
仕組み:
- whyts は、TypeScript 7 パッケージの
bin/tscを--checkers 1を指定して起動します。 - ネイティブコンパイラーは、各チェッカーの型に個別の番号を付けます。チェッカーが複数あると、トレース内の型 ID が重複します。チェッカーを 1 つにすることで、各 ID が一意に保たれます。
- ネイティブトレースはソース位置を UTF-8 のバイトオフセットで保存します。whyts はこれを UTF-16 のオフセットに変換します。
- ネイティブの
types_0.jsonファイルは、宣言パスを小文字で保存します。whyts はそれらを実際のファイル名と照合します。 - TypeScript 7 には、モジュール解決用の JavaScript API がありません。whyts はインポートグラフを構築し、独自の
typescript5.x または 6.x パッケージで tsconfig を読み取ります。
既知の制限:
- レポートと JSON では、実行が実験的であることが示されます。
compilerはnative、experimentalはtrue、checkersは1です。 - チェック時間は 1 つのチェッカーから取得します。通常の
tsc実行ではより多くのチェッカーを使うため、Check time が異なる場合があります。両者を比較しないでください。 - ネイティブコンパイラーは Node.js のプロセスではないため、
--max-old-space-sizeは効果がありません。whyts は警告を表示し、このオプションを無視します。 - ネイティブコンパイラーは
Dump types timeの行を出力しません。レポートには表示されず、summary.dumpTypesSecondsはnullになります。 - グラフとネイティブコンパイラーが報告するファイル数が異なる場合、whyts は警告を表示します。2 つのコンパイラー間でモジュール解決が異なる可能性があります。その場合、インポートグラフの調査結果は不確かなものとして扱ってください。
- whyts 自身の TypeScript で tsconfig を読み取れない場合、エラーで停止します。TypeScript 7 のみが認識する設定が原因となる場合があります。
- Linux x64 で TypeScript 7.0.2 のみをテストしました。Windows、macOS、その他の 7.x バージョンではテストしていません。
仕組み
- プロジェクトにインストールされている TypeScript コンパイラーを解決します。見つからない場合は、同梱の 5.9 コンパイラーにフォールバックします。TypeScript 7 パッケージがある場合は、ネイティブコンパイラーが選択されます。
- JSONC と
extendsを含め、コンパイラー API を使って有効な設定を読み取ります。 - ソース/インポートグラフを構築し、読み込まれた型パッケージを調べます。
--noEmit、--extendedDiagnostics、--generateTraceを指定して、そのコンパイラーを実行します。- 各ファイル/スレッド内で時間的にネストされたソースチェックをグループ化し、包含関係にある型比較を照合します。選択された型 ID を
types.jsonからストリーミングし、構造的な調査結果より先に測定値を順位付けします。 - 一時的なトレース/キャッシュファイルを削除します。
コンパイラーには、新しい一時的な増分キャッシュが渡されます。既存のビルドキャッシュは読み取られず、上書きもされません。ソースファイルと通常のビルド出力は変更されません。パッケージのライフサイクルスクリプトは実行されず、CLI からネットワークリクエストが送信されることもありません。ただし、依存関係のインストールや CLI の取得では、当然ながらネットワークにアクセスする場合があります。
計測範囲
- キャッシュを空にした状態での、トレース付き型チェックを計測します。
--runsを指定すると、キャッシュを空にした状態でのトレースなしチェックも計測します。Next.js のビルド、バンドル、テスト、言語サーバーの時間を計るものではありません。 - トレースの生成と空のキャッシュは計測時間に影響します。コンパイラー、フラグ、ハードウェア、トレースモードを揃えて実行結果を比較してください。レポートの実時間計測には、それより前に行われるグラフ解析は含まれません。
- 記録されたチェック区間は包括的で、式/型イベントはサンプリングされる場合があります。同じファイル、ソースチェック、チェーンルート、型ペアの重複区間は統合されます。最も時間のかかったファイルチェック上位5件と、プロジェクト内ファイルチェック上位5件は別々に報告されます。さらに、ソースチェーン上位5件(プロジェクト内を優先)、各チェーンにつき最大5件のメンバーと、チェーンに含まれる比較上位3件、型比較全体の上位5件が報告されます。個別のソースチェック上位5件は、互換性維持のため引き続き JSON に含まれます。これらのリストには重複があり、合計してチェック時間全体を算出することはできません。
- 比較は、その全区間が同じプロセス/スレッド上でソースチェック区間内に収まる場合に限り、最も内側に記録されたソースチェックに関連付けられます。チェーンには、そのメンバー内に含まれる比較も集計されます。これらの比較は、選択されたフォーカス式の外で発生する場合があります。チェックが記録されていない場合、位置情報がない場合、または型記述子を利用できない場合は、詳細度が下がります。whyts は欠落した因果関係のチェーンを推測しません。
- 同じファイル内の単一のソースチェックチェーンが、そのファイルに記録されたチェック時間の80%以上を占める場合、
check-hotspotの指摘は省略されます。チェーンの指摘ですでに同じ時間が報告されるためです。このファイルは「プロジェクト内ファイルチェック区間の記録時間上位」リストには引き続き表示されます。しきい値はsrc/analyze.jsのCHAIN_COVERAGE_THRESHOLD(0.8)です。 - バレルの到達数では、型のみのエッジを含む、観測されたソースエッジが数えられます。ファイルがすでに設定済みのルートである場合があり、直接インポートしてもプログラムのサイズが変わらないことがあります。
- グラフの走査量を抑えるため、バレル候補のチェックは最大40件で、再エクスポート数の多い順に評価され、上位5件が報告されます。128 MiB を超えるトレースファイルは解析されません。型記述子は64 KiB単位でストリーミングされ、走査予算は1 GiB、レコードごとの予算は4 MiB、保持データの予算は16 MiBです。選択された ID が見つかると走査を停止するため、残りのファイルは検証されません。制限に達した場合や ID が見つからない場合は警告が表示されます。ファイルサイズだけを理由に型解決が無効になることはありません。コンパイラーの出力は16 MiBに制限されます。ソーススニペットは240文字まで、型ラベルは160文字までです。
- モノレポではリーフ tsconfigを選択してください。whyts は
tsc --buildを実行せず、参照先のプロジェクトもビルドしません。参照先の宣言出力が欠落または古い場合、結果に影響することがあります。 diagnosticsのTypes、Instantiations、Memory usedはトレース実行から取得されます。トレースによってこれらの値は大きくなります。TypeSpec コンパイラープロジェクトでは、トレース付きの実行で10,480,436 types が報告されました。以前のトレースなしのテスト実行では135,624 types が報告されました。0.4ではトレースなしの実行を再度行っていません。これらを通常のtsc --extendedDiagnosticsの出力と比較しないでください。- whyts は、JavaScript 版 TypeScript コンパイラーの 5.x と 6.x をサポートしています。ネイティブ TypeScript 7 コンパイラーは実験的にサポートしています(TypeScript 7 のセクションを参照)。IDE や別のビルドフレームワークが使用するプラグインはプロファイルされません。
- 静的グラフのヒューリスティックでは、特定の型やファイルが特定のパフォーマンス低下を引き起こしていると断定できません。自動編集や修正は行いません。
トレースをさらに調べるには、Microsoft の analyze-trace を使用してください。コンパイラーのドキュメント:extendedDiagnostics、generateTrace、パフォーマンスに関するガイダンス。
開発
npm ci --ignore-scripts
npm run check
npm test
npm pack --dry-run
テストでは、インポートチェーン、パスエイリアス、循環参照、動的インポート、継承された設定、実際に読み込まれた重複型、ネストされたトレースイベントと順不同のトレースイベント、ソース位置をまたぐチェーンのグループ化、スレッド/ファイルの分離、コメントを考慮した宣言、UTF-8のチャンク境界と予算を考慮した選択的な記述子のストリーミング、記述子の欠落、プロジェクトの優先順位付け、バレルのルートの重複、ターミナルのサニタイズ、実際のトレースを使用したチェック、キャッシュの保持、コンパイラーエラー、JSON出力、タイムアウトを網羅しています。さらに、実行順序、ウォームアップの除外、サマリー値、ノイズルール、比較前のチェック、検出結果のオフライン照合をテストしています。MCPテストでは、生のstdioクライアントを使ってwhyts mcpを起動し、ツールリスト、簡潔な結果、エラー経路、進捗通知、実行中のコンパイラーの停止を確認します。GitHub Actionsでは、Linux、Windows、macOSでNode 20/22/24を実行します。
実装にはプレーンなESM JavaScriptを使用しているため、チェックアウトした状態でビルド手順なしに実行できます。必須の依存関係はTypeScriptのみです。MCPサーバーは別パッケージのwhyts-mcpにあり、さらに2つのパッケージを追加します。
バグ報告には、小さな再現例、Node/TypeScriptのバージョン、期待する出力と実際の出力を添えると役立ちます。報告やトレースには、非公開のファイル名、ソースの断片、型名が含まれることがあります。共有する前に内容を確認してください。
MITライセンス。Musa Toktasが作成しました。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。