Restrict sibling folder walking for @source patterns
Tailwind CSS now limits the directory traversal of @source patterns so that only the explicitly matched files are scanned, eliminating costly sibling scans that could stall the build.
背景
@source が具体的なファイルを指す場合でも、ベースディレクトリ全体が走査されていたことがパフォーマンスのボトルネックになっていました。Issue #20255 では @source "../../app.config.ts" が原因でスキャンが約 22 秒でハングし、ビルドが進まない状態が報告されています。最適化前はベース "/Users" 以下の全ファイルが走査され、結果は !app.config.ts で除外されるだけでしたが、実際の I/O 負荷は変わりませんでした。
技術的な変更
crates/oxide/src/scanner/sources.rs に新たな制限ロジックが導入されました。FxHashSet のインポートと共に、ベースごとに一度だけ * で全体を無視し、続けて対象パターンを ! で再有効化するコードが追加されています。
use fxhash::{FxHashMap, FxHashSet};
// 省略
// ベースディレクトリに対して * を一度だけ追加
if !unrestricted_roots.contains(&base) {
rules.push(Rule::IgnoreAll);
rules.push(Rule::Include(pattern));
}
このロジックは 同一ベースへの複数パターン でも * が二重に追加されないよう unrestricted_roots を参照し、@source "./src/foo.ts" と @source "./src/bar.ts" のケースで正しく * が一回だけ出現します。さらに、パターン中に * が残る中間フォルダがある場合は、親セグメントごとに逆ルール(!/ba*/、!/ba*/*.html)を生成して、期待通りのディレクトリ走査を実現しています。
テストコード (crates/oxide/tests/scanner.rs) も 200 行余り追加され、
- 単一ファイルソースの制限
- 同一ベースでの複数ソース統合
- 後続の @source not … が前方の制限を上書きできること
を検証しています。
設計判断
ベースディレクトリを * で全面的に無視し、必要なパターンだけを ! で再包括する方式 が選択されました。この設計は Gitignore の挙動と同等であり、既存のスキャナロジックと最小限の差分で実装できます。さらに、* の重複排除と順序保持により、後続の除外ルールが前方の制限を正しく上書きできる点が意図的に保たれています。広範な Auto/External ソースや ** を含むパターンは unrestricted_roots に分類され、制限の対象外とすることで、意図しない sibling の隠蔽を防いでいます。
まとめ
この変更により、@source が指す具体ファイルの走査がベースフォルダ全体への無駄な I/O を回避し、ビルド時間が数秒から数ミリ秒規模に短縮されます。設計は Gitignore に倣ったシンプルかつ拡張性のあるパターン処理で、既存のスキャナ挙動との互換性も維持しています。