Viteエイリアスが`@plugin`のパッケージ解決を壊す問題を修正
@tailwindcss/vite 4.2.3で追加されたVite aliasesサポートが、@をエイリアスとして使う環境で@tailwindcss/typography等のパッケージ解決を破壊していた問題を修正しました。Viteのリゾルバーが絶対パスを返せない場合は@tailwindcss/nodeのフォールバック解決に委ねる処理を追加しています。
背景
4.2.3でViteのresolve.aliasオプションに対応したことで、エイリアス経由のパス解決が可能になりました。しかし@という単一文字をエイリアスとして登録している場合、@tailwindcss/typographyのようなパッケージ名のプレフィックスとも衝突してしまいます。
Viteのリゾルバーは@tailwindcss/typographyを@エイリアスで解釈しようとしますが、ローカルファイルとして解決できないため失敗します。#19946では、vite.configに以下のようなエイリアス設定を持つユーザーがビルドエラーを報告しています。
resolve: {
alias: [
{ find: '@', replacement: fileURLToPath(new URL('.', import.meta.url)) },
],
}
この設定自体は一般的なプロジェクトルートエイリアスですが、4.2.3以前は正常に動作していたため、リグレッションとして報告されました。
技術的な変更
packages/@tailwindcss-vite/src/index.tsの customCssResolver と customJsResolver に、Viteのリゾルバー結果を検証するガード処理を追加しました。
変更前:
customCssResolver = async (id: string, base: string) => {
let resolved = await cssResolver(id, base, false, isSSR)
if (resolved && !resolved.endsWith('.css')) return undefined
return resolved
}
customJsResolver = (id: string, base: string) => jsResolver(id, base, false, isSSR)
変更後:
customCssResolver = async (id: string, base: string) => {
let resolved = await cssResolver(id, base, false, isSSR)
if (!resolved) return
if (resolved === id) return
if (!path.isAbsolute(resolved)) return
if (!resolved.endsWith('.css')) return
return resolved
}
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
}
追加されたガード条件は3つです:
-
resolved === id: リゾルバーが入力をそのまま返した場合(解決できていない) -
!path.isAbsolute(resolved): 絶対パスでない場合(エイリアスが部分的にしか展開されていない) - 以前からある
!resolved.endsWith('.css'): CSS以外のファイルへの解決をCSSリゾルバーで弾く
これらのガードを抜けない場合はundefinedを返し、@tailwindcss/nodeの解決ロジックへのフォールバックを促します。同様のロジックはEnvironment APIを使うコードパス(elseブランチ)にも対称的に適用されています。
またcustomJsResolverは従来同期関数として定義されていましたが、今回の変更でasync関数に統一されました。
設計判断
Viteのリゾルバーを信頼する条件として「絶対パスへの変換」を必須とする設計が採用されました。
Viteのエイリアスリゾルバーは、エイリアスにマッチしない識別子(例:@tailwindcss/typographyのうち@がエイリアスにマッチするケース)を処理した際、絶対パスではなく元の識別子や相対パスに近い値を返すことがあります。path.isAbsolute()によるチェックはこの曖昧さを排除し、「Viteが確実にファイルシステム上のパスへ解決できた」場合のみViteの結果を採用するシンプルかつ堅牢な判定基準となっています。フォールバック先の@tailwindcss/nodeはNode.jsのモジュール解決に従うため、npmパッケージの解決はそちらに委ねられます。
まとめ
Viteのリゾルバー結果に「絶対パスであること」という検証を加えることで、エイリアスの部分マッチによる誤解決を防ぎつつ、npmパッケージの解決はNode.jsの機構に委ねる責務分離が実現されています。@importと@pluginの両方に同じガードが適用されたことで、Viteエイリアスとパッケージ名が共存する環境での信頼性が向上しました。