Windows の `--watch` モードで `@source` が指す不存在ディレクトリが原因のクラッシュを防止
@tailwindcss/cli の --watch フラグ使用時に、Windows 環境で存在しないディレクトリを指定した @source が原因でプロセスが異常終了していた問題を解決しました。ディレクトリ存在チェックを導入し、ウォッチャー作成段階で安全に除外するよう変更されています。
背景
Windows で @tailwindcss/cli --watch を実行し、@source "unknown-folder/**/*" のように実体のないパスを指定すると、内部のディレクトリウォッチャー生成が失敗し Invalid handle エラーでプロセスが終了していました。原因は @parce/watcher が不存在ディレクトリに対しても監視設定を行おうとしたためです。PR はこのシナリオを再現するテストを追加し、Windows での失敗を明示的に捕捉できるようにしました。
この問題は、watchDirectories が単に scanner.normalizedSources の base パスを列挙していたことに起因し、実際のファイルシステム上にディレクトリが存在しないことを考慮していなかった点にあります。結果として、@parce/watcher が無効なハンドルを取得し、Node のランタイムが例外をスローしていました。
技術的な変更
watchDirectories 関数が非同期化 され、各 @source のベースパスを実際に確認するロジックが追加されました。具体的には path.resolve → fs.realpath(失敗時は元パス) → fs.stat を順に実行し、stat.isDirectory() が真の場合にのみディレクトリパスを結果に含めます。不存在またはファイルであるケースは空配列で置き換えられ、最終的に重複除去された配列が返されます。
@@
-function watchDirectories(scanner: Scanner) {
- return [...new Set(scanner.normalizedSources.flatMap((globEntry) => globEntry.base))]
-}
+async function watchDirectories(scanner: Scanner) {
+ let directories = (
+ await Promise.all(
+ scanner.normalizedSources.map(async (globEntry) => {
+ let resolvedPath = path.resolve(globEntry.base)
+ let realPath = await fs.realpath(resolvedPath).catch(() => resolvedPath)
+
+ return fs
+ .stat(realPath)
+ .then((stat) => (stat.isDirectory() ? [realPath] : []))
+ .catch(() => [])
+ })
+ )
+ ).flat(1)
+
+ return Array.from(new Set(directories))
+}
createWatchers 呼び出し側の修正 では、非同期化された watchDirectories の結果を await で取得してから渡すよう変更しました。これにより、ウォッチャー作成前に不存在ディレクトリが除外され、クラッシュが防止されます。
@@
- await createWatchers(watchDirectories(scanner), async function handle(files) {
+ await createWatchers(await watchDirectories(scanner), async function handle(files) {
@@
- let newCleanupFunction = await createWatchers(watchDirectories(scanner), handle)
+ let newCleanupFunction = await createWatchers(await watchDirectories(scanner), handle)
テスト追加 により Windows 環境での再現ケースが検証されています。unknown-folder/**/* を指す @source を含む CSS をビルドし、--watch がクラッシュせずに出力ファイルが生成されることを確認しています。
設計判断
本修正は CLI 層でディレクトリの有無を判定し除外する 方針を採用しました。@tailwindcss/postcss や @tailwindcss/vite といった他の統合層に影響を与えず、@source 記述自体は変更せずに済むため後方互換性が保たれます。実装は watchDirectories に非同期チェックロジックを追加し、呼び出し側で await を付与するだけの最小限の変更です。
このアプローチはプラットフォーム固有の問題(Windows のハンドルエラー)に対処しつつ、既存のコードパスを極力そのまま維持する設計といえます。不存在ディレクトリが除外されることで、@parce/watcher が安全に初期化され、他の OS への振る舞いは変更されません。
まとめ
watchDirectories にディレクトリ存在確認を組み込み、呼び出し側で非同期取得するようにしたことで、Windows の --watch モードで不存在 @source が原因でクラッシュしていた問題が解消されました。テストにより Windows 環境でも安定してウォッチが機能することが確認され、CLI の信頼性が向上しています。ディレクトリを後から追加する場合は、現状 CLI の再起動が必要であることが PR の記述に残されています。