webpackローダーのキャッシュキーにリソースクエリを含めてキャッシュ汚染を修正

tailwindlabs/tailwindcss

@tailwindcss/webpack のローダーが、同一CSSファイルを異なる resourceQuery でインポートした際にキャッシュエントリを共有してしまうバグを修正しました。キャッシュキーを this.resourcePath(パスのみ)から this.resource(パス+クエリ)に変更することで、各エントリが独立したキャッシュを持つようになります。

背景

同一CSSファイルを異なるクエリパラメータ付きでインポートすると、全インポートが単一の CacheEntry を共有してしまう問題がありました。Webpackのローダーは this.resourcePath(ファイルパスのみ)と this.resource(パス+クエリ文字列)の両方を提供しますが、@tailwindcss/webpack はキャッシュキーの構築に this.resourcePath のみを使用していました。そのため、styles.css?entryAstyles.css?entryB のように同じファイルを異なるクエリで参照すると、同一の CacheEntry にマッピングされてしまいます。

この結果、あるエントリ向けに検出されたユーティリティクラスが別エントリのCSS出力に「漏れ出す」キャッシュ汚染が発生していました。マルチエントリ構成のwebpackプロジェクトでCSSの分離が保証されない、深刻な問題です。

技術的な変更

キャッシュキーの生成ロジックを getCacheKey 関数として切り出し、キーに使用するリソース識別子を resourceIdthis.resource)に統一しました。

変更前:

function getContextFromCache(inputFile: string, opts: LoaderOptions): CacheEntry {
  let key = `${inputFile}:${opts.base ?? ''}:${JSON.stringify(opts.optimize)}`
  // ...
}

変更後:

function getCacheKey(resourceId: string, opts: LoaderOptions): string {
  return `${resourceId}:${opts.base ?? ''}:${JSON.stringify(opts.optimize)}`
}

function getContextFromCache(resourceId: string, opts: LoaderOptions): CacheEntry {
  let key = getCacheKey(resourceId, opts)
  // ...
}

ローダー本体では、this.resourcePathinputFile として保持しつつ、キャッシュ参照には this.resourceresourceId として使い分けます。ファイルシステム操作(path.dirname(path.resolve(inputFile)) など)は引き続き inputFile を使用するため、クエリ文字列がパス解決に混入する副作用はありません。

let inputFile = this.resourcePath  // ファイルシステム操作用
let resourceId = this.resource     // キャッシュキー用(パス+クエリ)

エラー発生時のキャッシュ削除パスも getCacheKey(resourceId, options) を使うよう修正され、エラー時の再ビルド処理でも一貫したキーが使われるようになりました。

設計判断

this.resource をキャッシュキーに使い、ファイルI/Oには this.resourcePath を使い続ける という責務の分離が採用されています。

この設計により、styles.css?entryAstyles.css?entryB はそれぞれ独立したキャッシュエントリを持ちながら、両者のファイル読み込みは同一のパス(styles.css)を正しく参照します。キャッシュキーを変えるだけで、ファイルシステム周りのロジックは一切変更不要という、最小限の修正で問題を解決しています。

また、キャッシュキー生成を getCacheKey 関数に切り出したことで、通常パスとエラーパスで同じキー文字列を使う保証が得られます。変更前はキー文字列をインラインで2箇所に記述していたため、将来的な変更時に片方を更新し忘れるリスクがありました。

まとめ

この修正は、webpackの resourcePathresource の違いを正確に把握した上で、キャッシュキーの責務を正しいプロパティに割り当てた変更です。変更規模は小さいものの、マルチエントリ構成でのCSS出力の正確性を保証する上で重要な修正であり、キャッシュ設計における「キーは一意性を担保すべき識別子で構成する」という原則の好例といえます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
c0c969e6

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術的変更・設計判断(各論)→まとめ(結論)という3部構成が明確に適用されており、模範的な記事構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(`typescript:path`)およびGitHubのPRリンク記法(`[PR #1234](URL)`)が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Webpackのローダーやキャッシュの概念を前提としており、専門知識を持つエンジニアという対象読者に適切なレベルの内容です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成で、かつ各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が徹底されており、非常に高い可読性を実現しています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロック(変更前・変更後)は、提供されたDiff情報と完全に一致しています。ファイル名も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「resourceQuery」「CacheEntry」「this.resourcePath」「this.resource」「キャッシュ汚染」といった技術用語が、PRの文脈に沿って正確かつ効果的に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

キャッシュキーの変更理由と、それに伴うファイルシステム操作への副作用がない点についての説明は、PR DescriptionとDiffの内容に完全に裏付けられており、技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張は、PRのDescriptionやDiff内のコード変更から直接裏付けが取れます。「設計判断」セクションの解説もコードから読み取れる事実に基づいており、ハルシネーションは検出されませんでした。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

記事内で言及されているPR番号「#19723」は正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「webpackローダーのキャッシュキーにリソースクエリを含めてキャッシュ汚染を修正」は、PRのタイトルと内容を正確かつ分かりやすく要約しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR情報に含まれないバージョンサポート状況やリリース日程などの外部知識は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「修正しました」という過去形の表現が使われており、PRが完了した事実と一致しています。時間表現の歪曲はありません。