トランジティブ依存削除時に @tailwindcss/cli が回復できるようになった
@tailwindcss/cli の --watch モードで、トランジティブな依存ファイルが削除された場合でも、削除後にファイルが復元されると自動で正常なビルドに戻るようになりました。この変更により、依存削除で発生していたスタック状態が解消され、開発者は CLI を再起動する必要がなくなります。
背景
Tailwind CSS のビルドは テンプレート と 構成に関わる依存 の 2 種類に分かれ、構成ファイルやプラグインが変化したときは フルリビルド が必須です。 @tailwindcss/cli はこれらの依存を fullRebuildPaths として監視し、変更があれば再コンパイルを行いますが、削除イベントは if (event.type === 'delete') return で無条件に除外されていました。結果として、依存ファイルが削除された状態でビルドエラーが起きても、ウォッチャーは何も再ビルドせずに古い CSS を出し続け、ユーザーは手動で CLI を再起動しなければなりませんでした。Issue #20113 で報告されたこの挙動は、削除された依存はフルリビルドのトリガーになるべき という設計と矛盾していました。
技術的な変更
削除イベントの転送とパスのバックアップ
packages/@tailwindcss-cli/src/commands/build/index.ts に以下のロジックが追加され、削除されたパスを files に登録した上で早期リターンします。
if (event.type === 'delete') {
files.add(event.path)
return
}
この変更により、後続のハンドラ (handle 関数) が fullRebuildPaths と比較でき、削除されたファイルがフルリビルド対象であれば即座にビルドが走ります。さらに、fullRebuildPaths の以前の状態を保持するために backupRebuildPaths という配列を導入し、コンパイラ作成が失敗した際にパスリストを復元できるようにしています。
let fullRebuildPaths: string[] = inputFilePath ? [inputFilePath] : []
let backupRebuildPaths = fullRebuildPaths
...
// コンパイラ作成成功後にバックアップを更新
backupRebuildPaths = fullRebuildPaths.slice()
エラーメッセージの可視化強化
例外捕捉部を改良し、エラーメッセージをツリー状に整形してコンソールに出力するようにしました。これにより、依存が見つからない旨のエラーが一目で把握でき、デバッグが容易になります。
eprintln(
[red('Error:'), dim('\u250C')]
.concat(`${err}`.split('\n').map(line => `${dim('\u2502')} ${line}`))
.concat(dim('\u2514'))
.join('\n'),
)
テストユーティリティの拡張
integrations/utils.ts に delete メソッドが追加され、テストケースからファイル削除操作をシミュレートできるようになりました。これに伴い、integrations/cli/index.test.ts でも削除後の再ビルドシナリオを検証する統合テストが追加されています。
async delete(filename: string): Promise<void> {
await fs.unlink(path.join(root, filename))
}
設計判断
削除イベントを完全に捨てずにパスだけを記録する 方針は、既存のウォッチロジックを最小限の改変で拡張する設計です。fullRebuildPaths のバックアップを保持しつつ、失敗時に元の状態へ安全にロールバックできるようにしたため、既存のフローバックエンドに大きな影響を与えません。また、エラーメッセージの整形は UI 改善の副産物であり、機能要件とは直接関係ありませんが、開発者体験を向上させる効果があります。全体として、既存インターフェースを維持しつつ、削除系イベントのハンドリングだけを追加する アプローチが選択されたことが読み取れます。
まとめ
今回の PR で、@tailwindcss/cli のウォッチャーは削除されたトランジティブ依存でも即座にフルリビルドを行い、復元後は自動で正常な状態に戻ります。バックアップパスの導入と削除イベントの転送により、CLI の信頼性が大幅に向上し、開発者は手動リスタートを避けられるようになりました。