ViteプラグインでtsconfigパスエイリアスがCSS/JSの解決に効かなかった問題を修正
@tailwindcss/viteにおいてViteのresolve.tsconfigPaths: trueが@importや@pluginに効かなかった根本原因は、ViteのリゾルバをaliasOnly: trueで呼び出していたことにあります。このPRではその値をfalseに変えることで、tsconfigパス解決を含む完全なVite解決パイプラインを通るようにしました。
背景
@tailwindcss/viteのCSSリゾルバとJSリゾルバはViteのリゾルバを呼び出す際にaliasOnly: trueを渡しており、これによりtsconfigパスの解決が機能しないという問題がありました。#19802で報告されたように、tsconfig.jsonに"@themes/*": ["./src/themes/*"]を定義し、CSSで@import "@themes/custom.css"と記述しても、Viteのdev serverはCan't resolve '@themes/custom.css'エラーを返していました。
aliasOnly: trueが渡されると、ViteのcreateIdResolverは@rollup/plugin-aliasプラグインのみを実行し、tsconfigパス解決を担う oxcリゾルバ をスキップします。そのため、resolve.aliasへの明示的なエントリ追加はワークアラウンドとして機能しましたが、resolve.tsconfigPaths: trueは一切効果がない状態でした。なお、Viteの内部CSSリゾルバ(css.ts)はaliasOnlyを省略(デフォルトfalse)して呼び出しており、完全な解決パイプラインを通過しています。
技術的な変更
packages/@tailwindcss-vite/src/index.tsにおいて、CSSリゾルバとJSリゾルバのaliasOnlyパラメータをtrueからfalseに変更しました。さらにCSSリゾルバでは、フルパイプラインによって非CSSファイルが解決されてしまうことを防ぐガード処理が追加されています。
変更前(Pre-environment API / Environment API):
// Pre-environment API
customCssResolver = (id: string, base: string) => cssResolver(id, base, true, isSSR)
customJsResolver = (id: string, base: string) => jsResolver(id, base, true, isSSR)
// Environment API
customCssResolver = (id: string, base: string) => cssResolver(env, id, base, true)
customJsResolver = (id: string, base: string) => jsResolver(env, id, base, true)
変更後(Pre-environment API / Environment API):
// Pre-environment API
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)
// Environment API
customCssResolver = async (id: string, base: string) => {
let resolved = await cssResolver(env, id, base, false)
if (resolved && !resolved.endsWith('.css')) return undefined
return resolved
}
customJsResolver = (id: string, base: string) => jsResolver(env, id, base, false)
CSSリゾルバにif (resolved && !resolved.endsWith('.css')) return undefinedというガード節が追加されている点が重要です。フルパイプラインでは非CSSファイルも解決対象になりうるため、解決結果が.css拡張子を持たない場合はundefinedを返してCSSリゾルバとしての責務を明示的に外れるようになっています。
設計判断
aliasOnlyをfalseにすることで生じる副作用に対するガード処理の追加が、このPRの設計上の核心です。
aliasOnly: false(フルパイプライン)では@rollup/plugin-aliasが先に実行されるため、resolve.aliasの動作は引き続き保証されます。一方、フルパイプラインはCSSファイル以外も解決しうることから、CSSリゾルバが誤って非CSSリソースを返す可能性が生まれます。.endsWith('.css')によるフィルタリングはこの問題を最小限のコード変更で抑止しています。JSリゾルバにはこの追加ガードがない点も注目に値します。JSリゾルバの用途(@pluginなど)ではフルパイプラインの結果をそのまま使うことが想定されており、拡張子による絞り込みは不要と判断されています。
まとめ
たった1つのパラメータ変更(aliasOnly: true → false)と、副作用を防ぐガード節の追加により、resolve.tsconfigPathsが@importと@pluginに対して正しく機能するようになりました。Viteのリゾルバ内部の動作を正確に把握した上で、最小限の変更で問題を根本解決した修正といえます。