`@plugin` がブラウザCSS エントリを誤って解決するリグレッションを修正

tailwindlabs/tailwindcss

@tailwindcss/vite において、@plugin "daisyui" のようなパッケージ指定が JavaScript エントリではなく browser フィールドのCSS ファイルに解決される不具合を修正しました。2段階のリゾルブ戦略により、Vite エイリアスと tsconfig パス解決の両立を維持しながら、このリグレッションを解消しています。

背景

#19803aliasOnly フラグを true から false に変更したことで、resolve.tsconfigPaths: true@plugin 解決に効くようになりました。しかしこの変更が、意図しない副作用を生みました。

aliasOnly: false の状態では Vite のフルリゾルバーパイプラインが走るため、daisyUI のように package.jsonbrowser フィールドにCSS ファイルを指定しているパッケージでは、Vite がそのCSS エントリを優先して返してしまいます。結果として Tailwind が .css ファイルを JavaScript プラグインとして読み込もうとし、#19950 で報告された次のエラーが発生していました。

Unknown file extension ".css"

技術的な変更

customJsResolver を2段階のリゾルブ戦略に書き直し、エイリアス優先解決とフルパイプライン解決を順に試みる構成にしました。

変更前:

customJsResolver = async (id: string, base: string) => {
  let resolved = await jsResolver(id, base, false, isSSR)
  if (!resolved) return
  if (resolved === id) return
  if (!path.isAbsolute(resolved)) return
  return resolved
}

変更後:

customJsResolver = async (id: string, base: string) => {
  // Resolve Vite aliases first so `@plugin "@/foo"` keeps working, but
  // let bare package specifiers fall through to Node-style resolution.
  let resolved = await jsResolver(id, base, true, isSSR)
  if (resolved && resolved !== id) {
    if (path.isAbsolute(resolved)) return resolved
    if (resolved[0] === '.') return path.resolve(base, resolved)
  }

  // Fall back to Vite's full resolver for features like tsconfigPaths,
  // but reject CSS results since plugins must resolve to executable code.
  resolved = await jsResolver(id, base, false, isSSR)
  if (!resolved) return
  if (resolved === id) return
  if (!path.isAbsolute(resolved)) return
  if (resolved.endsWith('.css')) return
  return resolved
}

第1フェーズでは aliasOnly: true でリゾルバーを呼び出します。@/foo のような Vite エイリアスが絶対パスや相対パスに解決された場合はその結果をそのまま返し、処理を完了します。エイリアス解決に失敗したベアパッケージ指定(daisyui 等)は第1フェーズをスルーし、第2フェーズへ進みます。

第2フェーズでは aliasOnly: false でフルリゾルバーパイプラインを実行し、tsconfig パス解決などの高度な機能を有効化します。ここで得た解決結果が .css で終わる場合は undefined を返し、Tailwind 内部のフォールバックリゾルバーに処理を委ねます。この同じロジックが、Vite 4系向けの env を受け取る実装にも対称的に適用されています。

設計判断

ファイル拡張子チェック(.css ガード)を第2フェーズにのみ適用する設計が採用されました。

PR の説明では、ハードコードしたJS拡張子リストで許可する方式ではなく、.css を明示的に拒否する方式が選ばれています。JavaScript の拡張子は .js / .mjs / .ts / .mts など多様であり、ホワイトリスト管理は漏れが生じやすいためです。一方でCSS を browser エントリに置くパターンは daisyUI のような実際のパッケージで確認されたアンチパターンであり、明確に拒否すべき対象として扱っています。

第1フェーズでエイリアスを先に解決することで、@plugin "@/foo" のような Vite エイリアスが意図しくフルパイプラインに流れ込むことも防いでいます。2段階構成にすることで、「エイリアス解決」と「tsconfig パス解決」という異なる機能を独立したフェーズに分離し、それぞれの副作用を制御可能にしている点が本変更の核心です。

まとめ

本PRは、aliasOnly フラグの変更で生じたリグレッションを、リゾルブ処理を2フェーズに分割することで解決しています。エイリアス解決とフルパイプライン解決を段階的に試みつつ、CSS エントリへの誤解決を明示的に拒否することで、daisyUI のような browser フィールドにCSS を持つパッケージと tsconfig パス解決の双方に対応できる堅牢な設計を実現しました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
8e6db9d6

この記事は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```)やPR/Issue番号のリンク([#123])など、カスタムMarkdown構文が正しく使用されています。

対象読者への適合性 ✓ PASS

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

ViteのリゾルバーやTailwind CSSのプラグイン機構に関する専門的な内容であり、対象読者であるエンジニアに適した技術レベルと語彙で書かれています。

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

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

各セクションが総論→各論の構成になっており、各段落もトピックセンテンスで始まるなど、パラグラフ・ライティングの原則がよく守られており、非常に読みやすいです。

Diff内容との照合 ⚠ WARNING

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

「変更後」のコード引用は正確ですが、「変更前」のコードはDiffに存在するコードを読者が理解しやすいように簡略化・一般化したもので、厳密にはDiffの削除部分と一致しません。ただし、技術的な理解を妨げるものではありません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`aliasOnly`, `リゾルバーパイプライン`, `ベアパッケージ指定`など、PRの文脈に沿った技術用語が正確かつ効果的に使用されています。

説明の技術的正確性 ✓ PASS

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

2段階のリゾルブ戦略や、CSSファイルへの解決を拒否するロジックなど、技術的な変更点に関する説明がDiffの内容と一致しており、正確です。

事実の突合 ✓ PASS

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

記事内のすべての主張(リグレッションの原因、daisyUIの事例、エラーメッセージなど)は、提供されたPRのDescriptionやDiffの内容によって裏付けられており、ハルシネーションは見られません。

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

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

PR番号(#19949)や関連Issue/PR番号(#19803, #19950)が正確に記載されています。

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

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

記事のタイトル「@plugin がブラウザCSS エントリを誤って解決するリグレッションを修正」は、PRのタイトルと内容を的確に要約しています。

外部知識の正確性 ✓ PASS

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

記事の内容は提供されたPR情報の範囲内に留まっており、無関係な外部知識の追加や捏造はありません。

時間表現の正確性 ✓ PASS

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

記事内の時間表現はPRで記述された事象の前後関係を正確に反映しており、誤解を招く表現はありません。