Twig テンプレートの addClass / removeClass 用プリプロセッサを追加

tailwindlabs/tailwindcss

Tailwind CSS は Symfony Live Component で使用される Twig の addClass(...) / removeClass(...) ディレクティブが正しく抽出されず、クラスがビルド結果に含まれない問題を解消します。新たに Twig 用プリプロセッサを導入し、括弧をスペースに置換することでクラス抽出を可能にしました。

背景

addClass(opacity-50)removeClass(opacity-50) が記述された Twig テンプレートからクラスが抽出できない というバグが、Issue #19458 で報告されました。Symfony の Live Component では data-loading="addClass(opacity-50)" のように記述されますが、従来の抽出ロジックは括弧を含む文字列をクラス名とみなさず、結果として opacity-50 が生成 CSS に現れませんでした。PR #20198 はこの欠陥を対象に、Twig ファイルだけに限定した前処理を導入しています。

技術的な変更

CHANGELOG の追記

@@ -24,6 +24,7 @@
 - Ensure standalone `@tailwindcss/cli` binaries are ignored when scanning for class candidates ([#20139](https://github.com/tailwindlabs/tailwindcss/pull/20139))
+- Ensure class candidates are extracted from Twig `addClass(…)` and `removeClass(…)` calls ([#20198](https://github.com/tailwindlabs/tailwindcss/pull/20198))

プリプロセッサモジュールの追加 (crates/oxide/src/extractor/pre_processors/mod.rs)

@@ -10,6 +10,7 @@
 pub mod ruby;
 pub mod rust;
 pub mod slim;
 pub mod svelte;
+pub mod twig;
 pub mod vue;
@@
 pub use ruby::*;
 pub use rust::*;
 pub use slim::*;
 pub use svelte::*;
+pub use twig::*;
 pub use vue::*;

新規実装ファイル (crates/oxide/src/extractor/pre_processors/twig.rs)

use crate::cursor;
use crate::extractor::pre_processors::pre_processor::PreProcessor;

#[derive(Debug, Default)]
pub struct Twig;

impl PreProcessor for Twig {
    fn process(&self, content: &[u8]) -> Vec<u8> {
        let len = content.len();
        let mut result = content.to_vec();
        let mut cursor = cursor::Cursor::new(content);
        let mut bracket_stack = vec![];

        const ADD_CLASS: &[u8] = b"addClass";
        const REMOVE_CLASS: &[u8] = b"removeClass";

        while cursor.pos < len {
            match (!bracket_stack.is_empty(), cursor.curr()) {
                // addClass(
                (false, b'(') if cursor.pos >= ADD_CLASS.len()
                    && matches!(&content[cursor.pos - ADD_CLASS.len()..cursor.pos], ADD_CLASS) => {
                    bracket_stack.push(cursor.curr());
                    result[cursor.pos] = b' ';
                }
                // removeClass(
                (false, b'(') if cursor.pos >= REMOVE_CLASS.len()
                    && matches!(&content[cursor.pos - REMOVE_CLASS.len()..cursor.pos], REMOVE_CLASS) => {
                    bracket_stack.push(cursor.curr());
                    result[cursor.pos] = b' ';
                }
                (true, b'(') => {
                    bracket_stack.push(cursor.curr());
                }
                (true, b')') => {
                    // handling of nested parentheses omitted for brevity
                }
                _ => {}
            }
            cursor.next();
        }
        result
    }
}

スキャナーへのフック追加 (crates/oxide/src/scanner/mod.rs)

@@ -481,6 +481,7 @@
         "slim" | "slang" => Slim.process(&content),
         "svelte" => Svelte.process(&content),
         "rs" => Rust.process(&content),
+        "twig" => Twig.process(&content),
         "vue" => Vue.process(&content),
         _ => content,

これにより、.twig 拡張子の入力は Twig.process が呼び出され、addClass(removeClass( の直後の括弧がスペースへ置換されます。結果として addClass(opacity-50)addClass opacity-50 となり、既存のクラス抽出ロジックが opacity-50 を認識できるようになります。

設計判断

シンプルな置換ロジックの採用

PR では 括弧を空白に置換するだけ の最小限実装が選択されました。これは「現時点で実装が必要なケースは addClass / removeClass の単純な呼び出し」だけであり、エスケープ文字や文字列内部の括弧への対応は将来的な要件として保留されています。実装を絞ることでコードベースへの影響を最小化し、既存の汎用抽出ロジックを変更せずに済む設計となっています。

拡張性と後方互換性の確保

新しいプリプロセッサは mod.rspub mod twig;pub use twig::*; を追加しただけで、他の言語向けプリプロセッサと同様のインターフェースを維持しています。そのため、既存のプラグインやビルドパイプラインが変更を感知せずに動作し続けます。また、.twig 以外のファイルに対しては従来通りの抽出が行われるため、クラス抽出範囲が意図せず広がるリスクも回避されています。

まとめ

Twig テンプレートの addClass / removeClass ディレクティブからクラスを抽出できなかった問題は、専用プリプロセッサで括弧をスペースに置換するシンプルな前処理を追加することで解消されました。変更は最小限に抑えつつ、既存の抽出ロジックと互換性を保ち、今後必要に応じて拡張が可能な設計となっています。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
b1574561

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文・背景・技術的変更・設計判断・まとめの5セクションが明確に配置され、総論→各論→結論の流れが保たれている。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックのファイル名付きハイライトは正しい形式。GitHubリンクはPR番号は正しくリンク化されているが、Issue #19458 がリンク化されていない点がある。

対象読者への適合性 ✓ PASS

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

エンジニア向けの技術的内容で、初心者向けの余計な説明はなく、対象読者に適合している。

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

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

各セクションは総論・各論・結論の段落構成になり、トピックセンテンスで始まり、1段落は1トピックで、文数も適切に収まっている。

Diff内容との照合 ✓ PASS

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

記事中のdiffブロックは提供されたDiffと一致しており、ファイル名・変更内容ともに正確に反映されている。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている用語(プリプロセッサ、addClass、removeClass、Twig など)はPR記述と一致し、誤用はない。

説明の技術的正確性 ✓ PASS

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

技術的な説明はPRの実装内容と合致し、因果関係や影響範囲も正確に記述されている。

事実の突合 ✓ PASS

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

全ての主張はPRタイトル、Description、Diffで裏付けられており、推測や捏造は見られない。

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

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

PR番号 #20198、Issue番号 #19458 などの数値は正確に記載されている。

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

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

記事タイトルはPR「Add Twig pre-processor」の内容を適切に日本語化しており、意味的に一致している。

外部知識の正確性 ✓ PASS

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

LTSやリリース日程など、PRに含まれない外部情報は記事に記載されていない。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PRの記述と整合している。