`canonicalize`サブコマンドに`--stream`フラグを追加

tailwindlabs/tailwindcss

tailwindcss canonicalizeコマンドに--streamフラグが追加され、デザインシステムをメモリに保持したまま標準入力からクラス群を継続的に受け取って正規化結果を返せるようになりました。これにより、フォーマッターやエディタプラグインなどの非JSツールが、毎回の起動コストを払わずにTailwindクラスの正規化処理を繰り返し実行できます。

背景

フォーマッターやエディタプラグインのような非JS環境のツールには、Tailwindクラスを正規化する軽量な手段がありませんでした。既存のバッチモードは一度きりの使用には適していますが、繰り返し正規化を行うツールでは、実行のたびにデザインシステムのロードコストが発生するという問題がありました。

この課題はdiscussion #19736でも議論されており、ロングランニングプロセスとして起動し続けるサイドカーモデルが解決策として検討されていました。--streamフラグはまさにこのモデルを実現するもので、ツールがtailwindcss canonicalize --streamを一度起動すれば、その後は標準入力に候補グループを送り込むだけで正規化結果を得られます。

実際の使用例は以下の通りです:

$ echo -e "py-3 p-1 px-3\nmt-2 mr-2 mb-2 ml-2" | tailwindcss canonicalize --stream
p-3
m-2

技術的な変更

--streamフラグの追加は、CLIのオプション定義から実装までpackages/@tailwindcss-cli/src/commands/canonicalize/index.tsに集約されています。

まず、オプション定義に--streamが追加されました:

'--stream': {
  type: 'boolean',
  description: 'Read candidate groups from stdin line by line and write results to stdout',
  default: false,
},

runCommandLine関数内では、flags['--stream']が真の場合に新たにエクスポートされたstreamStdin関数へ処理を委譲します。streamStdin関数はnode:readlinecreateInterfaceを使って標準入力を1行ずつ読み取り、各行を候補グループとして正規化処理を実行して結果を即座に書き出します。空行はそのまま透過させる設計となっており、リクエストとレスポンスのペアのアライメントが崩れません。

出力フォーマットは既存の--formatフラグと組み合わせられます。テキスト形式(デフォルト)ではシンプルに正規化後のクラス文字列を1行ずつ出力し、JSON形式ではinputoutputchangedの3フィールドを持つオブジェクト配列として出力します。テストでその動作が検証されています:

test('streams json output when requested', async () => {
  let input = Readable.from('py-3 p-1 px-3\nmt-2 mr-2 mb-2 ml-2\n')
  let { stream: output, collect: collectOutput } = createOutput()

  await streamStdin({ css, cwd: path.dirname(css), format: 'json', input, output })

  expect(JSON.parse(collectOutput())).toEqual([
    { input: 'py-3 p-1 px-3', output: 'p-3', changed: true },
    { input: 'mt-2 mr-2 mb-2 ml-2', output: 'm-2', changed: true },
  ])
})

テストではnode:streamReadablePassThroughを用いてstdin/stdoutをモックしており、streamStdin関数がテスト可能なインターフェースとして独立してエクスポートされていることが確認できます。

設計判断

デザインシステムをプロセスライフタイム全体で保持する設計が採用されました。--streamモードではプロセス起動時に一度だけデザインシステムをロードし、その後の各行の処理では再ロードを行いません。これが「サイドカープロセス」としての主要な価値であり、ロードコストを償却できます。

空行のパススルー設計も注目に値します。リクエスト側が空行を区切りとして送信し、レスポンス側も空行を受け取ることで、非同期に複数の候補グループを送り込む場合でもリクエストとレスポンスの対応関係を行番号で追跡できます。createInterfaceによるライン単位の読み取りはこの設計に自然に適合します。

また、streamStdin関数をrunCommandLineから分離してエクスポートすることで、テストからI/Oをモックして注入できるようにしています。ReadableWritableを引数として受け取るインターフェースは、Node.jsのストリームAPIの慣用的なパターンに沿っており、テスタビリティと実装の分離を両立しています。

まとめ

--streamフラグは、Tailwindクラスの正規化を繰り返し行う外部ツールに対して、プロセスの再起動コストを排除したサイドカーモデルを提供します。stdin/stdoutの行単位プロトコルというシンプルなインターフェースにより、言語やプラットフォームを問わずあらゆるツールからの利用が可能となりました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
06fe6cd2

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)、背景・技術詳細・設計判断(各論)、まとめ(結論)という「総論→各論→結論」の構成が明確に適用されており、模範的です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```言語:ファイルパス)とGitHubのPR/Discussionリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Tailwind CLIやサイドカープロセスといった概念を前提としており、専門知識を持つエンジニアという対象読者に適合した内容です。

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

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

各セクションが総論→各論で構成され、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロックは、提供されたDiffの内容とファイル名を含めて正確に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「サイドカープロセス」「stdin/stdout」「正規化(canonicalize)」など、PRの文脈における技術用語を正確かつ適切に使用しています。

説明の技術的正確性 ✓ PASS

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

「空行のパススルー」や「テストでのI/Oモック」に関する説明は、Diff内のテストコードによって裏付けられており、技術的に正確です。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのDescriptionやDiff内のコードで裏付けられており、ハルシネーション(捏造)は見られません。「設計判断」セクションはコードの構造から論理的に導かれたもので、許容範囲です。

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

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

PR番号(#19796)と関連するDiscussion番号(#19736)が正確に記載されています。

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

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

記事のタイトルはPRのタイトル「Add `--stream` flag to `canonicalize` subcommand」の内容を正確に反映しています。

外部知識の正確性 ✓ PASS

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

PR情報に含まれないバージョンサポート状況やリリース日程などの外部知識は記載されていません。

時間表現の正確性 ✓ PASS

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

「追加され」「なりました」といった過去形の表現が使われており、完了した変更であるというPRの文脈と時間表現が一致しています。