PostCSS の `from` 未設定時にプロジェクトルート外を参照するバグを修正

tailwindlabs/tailwindcss

@tailwindcss/postcss プラグインが result.opts.from を受け取らない環境で、インポート解決のベースパスがプロジェクトルートの親ディレクトリになってしまうバグが修正されました。Turbopack を含む一部のバンドラーで発生していた Can't resolve 'tailwindcss' エラーが解消されます。

背景

PostCSS プラグインは必ずしも from オプションを受け取るとは限らず、Turbopack を含む一部のバンドラーは特定の CSS 入力に対して from なしでプラグインを呼び出します。この状況では result.opts.fromundefined となり、inputFile が空文字列 '' になります。

問題の核心は Node.js の path.resolve('') の挙動にあります。path.resolve('')process.cwd() を返し、さらに path.dirname(process.cwd()) はカレントディレクトリのを返します。結果として compileAst({ base: inputBasePath }) がプロジェクトルートの1つ上のディレクトリから tailwindcss を解決しようとし、以下のエラーが発生していました。

Can't resolve 'tailwindcss' in '<parent of CWD>'

プラグイン自体はオプション解析の早い段階で base = opts.base ?? process.cwd() を計算しており、この値を再利用することで適切なフォールバックが実現できます。

技術的な変更

packages/@tailwindcss-postcss/src/index.tsinputBasePath 計算に1行の三項演算子が追加されました。

変更前:

let inputBasePath = path.dirname(path.resolve(inputFile))

変更後:

let inputBasePath = inputFile ? path.dirname(path.resolve(inputFile)) : base

inputFile が空文字列(falsy)の場合に限り、既に算出済みの base 変数をそのまま inputBasePath として使用します。baseopts.base ?? process.cwd() で定められているため、ユーザーが opts.base を明示した場合はその値が、省略した場合は process.cwd()(プロジェクトルート)が使われます。

テストは packages/@tailwindcss-postcss/src/index.test.ts に追加されています。from オプションなしで processor.process('@import \'tailwindcss\'') を呼び出し、修正前は例外を投げていた箇所で非空の CSS が返されることを検証しています。

test('fallback to `base` directory when `result.opts.from` is not provided', async () => {
  let processor = postcss([
    tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
  ])

  let result = await processor.process(`@import 'tailwindcss'`)

  expect(result.css.length).toBeGreaterThan(0)
})

設計判断

プラグイン上部で既に算出済みの base 変数を再利用するアプローチが採用されました。

PR の説明に記されているように、この変更の意図は「sensible default (CWD) を与えつつ、明示的な opts.base が設定されている場合はそれを尊重する」ことです。base = opts.base ?? process.cwd() という既存のロジックがまさにこの要件を満たしており、新たなロジックを追加することなく再利用することで実現しています。

変更は1行の条件分岐追加のみであり、from が存在する通常ケースのコードパスは一切変更されていません。影響範囲は from が省略される環境に限定されるため、既存の動作に対するリグレッションリスクは最小限です。

まとめ

path.resolve('')process.cwd() を返し path.dirname でさらに1階層上がるという Node.js のパス解決の挙動を、プラグイン設計の文脈で正しく扱うための修正です。1行の変更でありながら、opts.base による明示設定の尊重とデフォルト動作の正確化を同時に達成しており、PostCSS プラグインの堅牢な設計指針を示す好例といえます。

記事メタデータ

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

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

「リード文(総論)→背景・技術的変更・設計判断(各論)→まとめ(結論)」という3部構成が明確に守られています。各セクションの役割がはっきりしており、読者が変更の全体像から詳細までをスムーズに理解できる構成です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きのシンタックスハイライト(```typescript:filepath```)およびPR番号のリンク記法([#19980](URL))は、ガイドラインに準拠して正しく使用されています。

対象読者への適合性 ✓ PASS

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

PostCSS、バンドラー、Node.jsの`path`モジュールなど、専門的なトピックを前提としており、専門知識を持つエンジニアという対象読者に適合しています。不要な初心者向けの解説はありません。

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

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

各セクションの冒頭に要旨を述べるパラグラフがあり、各段落はトピックセンテンスで始まっています。1段落1トピックの原則が守られ、段落長も適切であるため、非常に高い可読性を実現しています。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコード(`index.ts`の変更箇所、`index.test.ts`の追加テスト)は、提供されたDiff情報と完全に一致しています。ファイルパスの指定も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「PostCSS」「`result.opts.from`」「`path.resolve('')`」「`process.cwd()`」などの技術用語が、PRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

「`path.resolve('')`がカレントディレクトリを返し、`path.dirname()`でその親ディレクトリを参照してしまう」というバグの原因に関する説明は技術的に正確であり、PR Descriptionの内容とも一致しています。

事実の突合 ✓ PASS

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

記事内のすべての主張(Turbopackでの発生、Node.jsの挙動、解決策、テスト内容)は、提供されたPRのDescriptionやDiffによって裏付けられており、ハルシネーションは検出されませんでした。

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

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

PR番号(#19980)は正確に記載されています。

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

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

記事のタイトルは、PRの「`from`オプションがない場合に`base`へフォールバックする」という内容を、ユーザーが直面する「プロジェクトルート外を参照するバグ」という観点から的確に表現しており、内容との整合性が取れています。

外部知識の正確性 ✓ PASS

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

記事には、PR情報に記載のないバージョンサポート状況やリリース日程などの外部知識は含まれておらず、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

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

記事内の時間表現は、過去に行われた変更を正しく記述しており、PRの内容と矛盾しません。