スタンドアロン CLI バイナリをスキャン対象から除外
Tailwind CSS のスタンドアロン CLI がプロジェクトに配置された場合、実行ファイル内部のクラス名が誤って CSS ソースとして走査されていた問題を解決します。process.execPath を常に除外対象にすることで、実行ファイル名が変更されても安全にスキャンを回避します。
背景
この問題は、スタンドアロン CLI バイナリをプロジェクトディレクトリに置くと、Tailwind のクラス検出ロジックがバイナリ内部のテキスト(例: text-red-500、grid、flex)をソースファイルとみなしたことが原因でした。結果として、実行ファイル自体が CSS に混入し、不要なクラスが生成されていました。
既存の回避策は .gitignore や @source not ディレクティブで手動除外する方法でしたが、デフォルトで除外すべきという期待と合致していませんでした。CLI のバイナリがテキストとして読めること自体は設計上問題ではなく、スキャン対象の判定ロジックに起因していました。
この課題を根本的に解決することで、ユーザーが CLI をプロジェクト内に置いた際に余計な設定を求められないようにします。
技術的な変更
packages/@tailwindcss-cli/src/commands/build/index.ts に process.execPath を除外するロジック が追加されました。具体的には sources 配列へ次のエントリを push しています。
+ sources.push({
+ base: path.dirname(process.execPath),
+ pattern: path.basename(process.execPath),
+ negated: true,
+ })
このエントリは base に実行ファイルのディレクトリ、pattern に実行ファイル名、negated: true で除外フラグを設定し、スキャナーが 実行中のバイナリ を候補探索から除外することを保証します。名前ベースの除外ではなく実行パスそのものを基準にするため、バイナリ名がリネームされても同様に無視されます。
さらに integrations/cli/standalone.test.ts に新しい統合テストが追加され、バイナリをプロジェクトにコピーして実行した際に flex・grid・underline が生成された CSS に含まれないことを検証しています。
+test(
+ 'does not scan itself for candidates',
+ { /* 省略 */ },
+ async ({ root, fs, exec }) => {
+ /* バイナリコピーと実行 */
+ await exec(`${IS_WINDOWS ? binary : `./${binary}`} --input src/index.css --output dist/out.css`)
+ await fs.expectFileNotToContain('dist/out.css', [candidate`flex`, candidate`grid`, candidate`underline`])
+ },
+)
これらの変更により、従来の誤検出は解消され、CLI の配置パターンに依存しない安定したビルドが実現します。
設計判断
除外対象として ハードコードされた名前リスト を採用せず、process.execPath に基づく動的除外を選択した点が本変更の核心です。名前リストはリネームや将来的なバイナリ名変更に脆弱であり、メンテナンスコストが増大します。実行パスは常に正確で唯一無二な情報であるため、これを利用することでシンプルかつ堅牢な実装が可能になりました。
また、negated: true フラグのみを追加する形で既存の sources ロジックに介入したため、他のスキャン設定やプラグインとの互換性に影響を与えません。既存ユーザーが設定を変更する必要はなく、デフォルトで安全な動作が保証されます。
この設計は「最小限の侵入で問題を根本解決する」アプローチを体現しており、将来の拡張や別プラットフォームへの移植でも同様の手法を適用しやすくなっています。
まとめ
process.execPath を除外対象に加えることで、スタンドアロン CLI がプロジェクト内にあっても誤ってクラス候補として走査されなくなりました。ハードコードされた名前リストを回避し、リネーム耐性を確保したシンプルな実装です。この変更はデフォルト動作を改善し、ユーザーが余計な回避策を取る必要をなくすと同時に、既存のビルドフローやプラグイン互換性を保持します。