Claude Media
Claude Codeでビルドが遅い原因を絞る — tsc・webpack・Viteの計測ログの渡し方

Claude Codeでビルドが遅い原因を絞る — tsc・webpack・Viteの計測ログの渡し方

ビルドが遅いときは、型検査・変換・バンドルのどこが支配的かを先に測ります。tsc・webpack・Viteの計測ログをClaude Codeに渡し、直す段階を1つに絞る手順です。

ビルドが遅いとき、Claude Codeに「速くして」と頼むと、設定の一般論やキャッシュの追加が並びがちです。遅さの正体が型検査なのか、変換なのか、バンドルなのかを決めないまま直すからです。先に段階ごとの時間を測り、そのログを渡せば、直す場所は1つに絞れます。

ここでは、TypeScriptの型検査、webpack、Viteそれぞれの計測コマンドと、結果をClaude Codeに読ませる流れをまとめます。出力ファイルのサイズを減らす話はバンドルサイズ削減の記事、CIの待ち時間はGitHub Actionsの高速化にあります。ここで扱うのは、手元でbuildを走らせたときの所要時間です。

ビルドの時間は3つの段階に分けて見る

ビルドは、大きく3つの段階に分けられます。

  • 型検査: TypeScriptのコンパイラが、プロジェクト全体の型を照合する
  • 変換: トランスパイラやローダーが、ファイルごとにコードを書き換える
  • バンドル: モジュールをつなぎ、最適化して出力する

この3つは、直し方がまったく違います。型検査が重いのにvite.configを調整しても、時間は動きません。逆に、変換が重いのにtsconfigを触っても同じです。

Viteが型検査を持たない点は、切り分けの手がかりになります。Viteの公式ドキュメントには、.tsファイルはトランスパイルだけで型検査はしない、と書かれています。型検査はモジュールグラフ全体の知識が要るため、ファイル単位で動く変換とは仕事の性質が違う、という理由です。tsc && vite buildのように2つのコマンドをつないでいるなら、合計時間はどちらかの段階が支配しています。

webpackでts-loaderを使っている場合は、型検査と変換が同じ工程に混ざります。この混ざり具合が、次の節の「tscだけを単独で走らせる」測り方の動機になります。

型検査の時間はtsc --noEmit --extendedDiagnosticsで測る

TypeScriptの公式Wikiは、ビルドツール越しに遅さを調べるより、まずtsc単体を走らせることを勧めています。--extendedDiagnosticsを付けると、コンパイラがどこで時間を使ったかが出力されます。

mkdir -p build-logs
npx tsc --noEmit --extendedDiagnostics \
  > build-logs/tsc.txt 2>&1

--noEmitは出力ファイルを書かないフラグです。型検査の時間だけを見たいので、書き出しの時間を混ぜません。

出力で見る項目は、Wikiの説明では次のとおりです。

項目意味大きいときに疑うこと
Files意味プログラムに含まれるファイル数大きいときに疑うことinclude/excludeの誤りでnode_modulesなどを巻き込んでいないか
I/O Read time意味ファイルシステムの読み取り時間大きいときに疑うこと同上(includeしたフォルダの走査も含む)
Parse time意味字句解析と構文解析の時間大きいときに疑うことファイル数・行数が想定と合っているか
Bind time意味ファイル内で閉じる意味情報の構築時間大きいときに疑うこと単独で支配することは少ない
Check time意味型検査そのものの時間大きいときに疑うこと型が複雑な箇所、.d.tsの量

Wikiの出力例では、Check timeが1.07秒、Total timeが1.75秒でした。この例では型検査が最大の項目です。ただしTotal timeは各項目の合計にならず、重なっている時間や計測されていない処理があると明記されています。足し算で割合を出さず、どの項目が突出しているかを見ます。

FilesとLinesの値は、実際のプロジェクトと比べます。Wikiも、ファイル数や行数が想定とずれていたら--listFilesOnlyや--listFilesで中身を確かめるよう案内しています。

Check timeが大きいときの追加計測

Check timeが支配的で理由が分からないときは、--generateTraceでトレースを取ります。TypeScript 4.1以降のオプションで、出力先のフォルダを渡します。

npx tsc --noEmit --generateTrace build-logs/trace
npx @typescript/analyze-trace build-logs/trace

@typescript/analyze-traceは、時間のかかっている箇所を一覧にするユーティリティです。EdgeやChromeのabout://tracingにJSONを読み込んで手で見ることもできます。Wikiの注意書きによると、トレースにはファイルパスやソースコードの断片が含まれ得ます。外部に共有する前に中身を確かめてください。また、トレースの形式はバージョン間で安定していません。

webpackの時間はprofile: trueと--progress=profileで測る

webpackには、設定に書くプロファイルのオプションがあります。profile: trueにすると、統計と警告の情報を取り、モジュールごとの時間の要約もログに出ます。

// webpack.config.js
export default {
  // ...
  profile: true,
  parallelism: 1,
};

parallelism: 1を一緒に置くのは、設定リファレンスの指示です。並列のままだと計測値が正確にならず、1にするとビルド自体は遅くなります。計測のときだけ付け、終えたら外します。

進捗の段階ごとの時間は、CLIの--progress=profileで取れます。

npx webpack --progress=profile \
  > build-logs/webpack-progress.txt 2>&1
npx webpack --json build-logs/webpack-stats.json

--jsonは統計をファイルに書き出すオプションです。このファイルはすぐ大きくなります。Claude Codeに渡すときの扱いは、後の節で触れます。

ts-loaderを使っているときの比較

型検査と変換が混ざっているかを調べるには、先ほどのtsc --noEmitの時間と、webpackのビルド時間を並べます。webpackのビルドがtsc単体より大幅に長く、モジュールの時間にts-loaderが目立つなら、型検査をwebpackの中で走らせていることが候補です。

webpackの公式ガイドは、ts-loaderのtranspileOnly: trueで型検査を切り、fork-ts-checker-webpack-pluginで別プロセスに型検査を移す方法を挙げています。transpileOnlyはそれだけだと型検査が無くなるため、プラグインとの併用が前提です。

Viteの時間はvite build --profileと--debugで測る

Viteのbuildコマンドには、--profileと--debugがあります。--profileは組み込みのNode.jsインスペクターを起動し、.cpuprofileファイルを書き出します。

npx vite build --profile build-logs/vite
npx vite build --debug plugin-transform \
  > build-logs/vite-transform.txt 2>&1

.cpuprofileは、speedscopeのようなツールで開いて見ます。--debug plugin-transformは、ファイルごとの変換にかかった時間をログに出します。非同期の処理はタイミングが不正確になりやすいため、数字は目安として扱うよう、公式のパフォーマンスガイドにも書かれています。

Viteが遅いと感じたとき、疑う場所は3つあります。

  • コミュニティプラグインのtransformなどのフック。重い処理が走っていないか
  • 拡張子を省いたimport。Viteは候補の拡張子を順に試すため、暗黙のimportが多いほどファイルシステムへの問い合わせが増える
  • バレルファイル(export *で再エクスポートするindexファイル)。1つのAPIだけ使っても、同じフォルダのファイルが読み込まれる

最後の2つは、vite --debug transformのログで頻繁に使われるファイルを探す方法とも組み合わせられます。

計測ログをClaude Codeに読ませる

ここまでで、build-logs/に段階ごとのログがそろいます。次は、読ませる側の設計です。

ログは標準入力かパスで渡す

Claude Codeは非対話モード(-p)で標準入力を読めます。ビルドのエラーログを渡して原因を説明させる例は、公式ドキュメントにもあります。

cat build-logs/tsc.txt | claude -p \
  "Check timeを含む各項目から、型検査のどこが重いかを説明して"

標準入力は10MBが上限で、超えるとエラーで終了します。webpackの統計JSONのように大きいファイルは、パスをプロンプトに書いて渡します。

対話セッションでは出力の上限に注意する

対話セッションでClaude Codeにビルドを走らせた場合、Bashツールの出力には上限があります。成功した結果は約30,000文字までがそのまま渡り、それを超えるとセッションフォルダに保存されたファイルのパスと、先頭2,000文字までのプレビューが返ります。失敗した場合は約10,000文字の先頭と末尾の抜粋になり、ファイルパスは付きません。

計測ログのような長い出力は、> build-logs/xxx.txt 2>&1でファイルに落としてから読ませるのが確実です。そうしないと、末尾だけ、あるいはプレビューだけを根拠に結論が出ます。

所要時間にも上限があります。コマンドの既定のタイムアウトは2分で、Claudeがより長い時間を要求しても、上限は10分です。本番相当のビルドが2分を超えるプロジェクトでは、BASH_DEFAULT_TIMEOUT_MSとBASH_MAX_TIMEOUT_MSの環境変数で調整できます。

依頼文は「測る → 読む → 1か所だけ直す」の順にする

直す前に仕分けの表を出させると、当て推量が減ります。plan modeで次のように頼む形です。

build-logs/ の tsc.txt・webpack-progress.txt・vite-transform.txt を読んで、
次の表を出して。まだ何も変更しないこと。
- 段階(型検査 / 変換 / バンドル)ごとの所要時間
- 最も支配的な段階と、その根拠になった行
- その段階を直す案を、変更範囲の小さい順に3つ

実装に入るときは、1回の変更で触る段階を1つに決めます。型検査とバンドルの設定を同時に変えると、どちらが効いたのか分からなくなるためです。

CLAUDE.mdに計測の手順を残す

毎回同じ測り方で比べられるよう、コマンドと比較のルールをCLAUDE.mdに書いておきます。

## ビルドの計測
- 計測ログは build-logs/ に出力する(コミットしない)
- 型検査: npx tsc --noEmit --extendedDiagnostics
- 変更前後は同じコマンドを3回走らせ、中央値で比べる
- 1回の変更で触る段階は1つだけにする
- 変更後は build-logs/ の同名ファイルを上書きせず、-after を付けて残す

build-logs/は.gitignoreに入れておきます。3回の中央値という決め方は、CLAUDE.mdで運用を固定するための一例で、公式ドキュメントの推奨ではありません。

支配的な段階ごとの直し方

どの段階が重いかが決まったら、直す手段を選びます。以下は、公式ガイドに書かれている手段を段階別に並べたものです。

支配的な段階手段注意点
型検査手段include/excludeを直し、node_modulesを巻き込まない注意点exclude: ["node_modules"]だけでは深い階層に届かない場合がある。Wikiは["**/node_modules", "**/.*/"]を示す
型検査手段incrementalで.tsbuildinfoを保存し、2回目以降を速くする注意点初回の時間は変わらない
型検査手段skipLibCheckで.d.tsの検査を省く注意点型システムの正確さと引き換え。ライブラリ同士の型の食い違いを見逃す
型検査(webpack)手段ts-loaderをtranspileOnlyにし、型検査を別プロセスへ注意点型検査を戻し忘れない
変換(webpack)手段ローダーにincludeを付け、変換が必要なモジュールだけに適用する注意点全.jsにbabel-loaderを当てていないか
変換(webpack)手段cacheオプションで永続キャッシュを使う注意点キャッシュの削除タイミングを決める
変換(Vite)手段重いプラグインのtransformで、対象のid・キーワードを先に判定する注意点計測値は目安
解決(Vite)手段拡張子を明示し、バレルファイルを避ける注意点暗黙のimportが多いほど効く

Wikiによれば、isolatedModulesは「速くなる設定」ではありません。単一ファイル単位の変換で壊れる書き方を、早い段階でエラーにするためのフラグです。変換を別のツールに任せる構成の前提として使うもので、これを有効にしただけでは時間は縮みません。

ProgressPluginは、webpackの公式ガイドで、外すとビルド時間が縮み得る要素として挙げられています。計測を終えたら入れたままにするかを見直す価値があります。

切り分けの順番

切り分け

ビルドが遅いときの切り分け

  1. Qnpx tsc --noEmit 単体で、ビルドの大半の時間を使っていますか?
    答えがいいえのとき

    webpackかViteの工程に原因があります。次の問いに進みます。

    答えがはい
  2. QCheck timeが突出していますか?
    答えがいいえのとき

    Files・I/O Read・Parse timeが大きいなら、include/excludeと意図しないファイルの混入を疑います。

    答えがはい
  3. Qwebpackのprofile: trueで、特定のローダーやモジュールが目立ちますか?
    答えがいいえのとき

    目立たない場合は、プラグインとバンドル規模を疑います。ProgressPluginの有無や分割の設定を見ます。

    答えがはい

1つの段階だけを直して、同じコマンドで測り直す

直す対象は、計測値が最大の段階の1か所です。直したら同じコマンドで再計測し、前後を比べます。

つまずきやすい点

  • 計測条件が毎回違う: キャッシュの有無で時間が変わります。初回ビルドと2回目以降を分けて記録し、比べるときは同じ条件に揃えます。
  • profile: trueのまま並列度を1にし続ける: 計測用の設定です。通常のビルドに残すと遅くなります。
  • Total timeを各項目の足し算とみなす: Wikiが明記しているとおり、合計には一致しません。
  • 型検査を切って安心する: transpileOnlyやskipLibCheckは、型のエラーや食い違いを見逃す方向に働きます。CIでtsc --noEmitを別に回すなど、型検査の居場所を残します。
  • Claude Codeに結果を要約させて原文を渡さない: 要約は元の数字を落とします。ログそのものか、そのパスを渡します。

CRAからViteへ移った直後にビルド構成が変わった場合は、Create React AppからViteへの移行も参考になります。Vue 3プロジェクトで型検査の窓口をvue-tscにしている場合は、Vue 3の検証コマンドの記事に型検査の置き場所があります。

まとめ

ビルドが遅いときは、型検査・変換・バンドルのどこが支配的かを、段階ごとの計測ログで決めてから直します。tsc --noEmit --extendedDiagnostics、webpackのprofile: true、Viteの--profileと--debug plugin-transformで、材料はそろいます。

Claude Codeには、ログをファイルに落として渡し、plan modeで仕分けの表を出させる順が安全です。表で最大の段階を1つ選び、直して同じコマンドで測り直します。型検査を切る手段は速度と引き換えに検出力を落とすので、型検査の居場所を別に残すところまでを1セットにします。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/headless
  2. code.claude.com/docs/en/tools-reference
  3. github.com/microsoft/TypeScript/wiki/Performance
  4. webpack.js.org/guides/build-performance
  5. vite.dev/guide/performance
この記事を共有:XはてブLinkedIn