MDXファイル内のドット付きクラス名の抽出不具合を修正
Tailwind CSS v4.2.1では、MDXファイル内で p-2.5 のようなドット付きユーティリティクラスが正しく抽出されない不具合が修正されました。これにより、MDXとMarkdownディレクティブ構文の両方で安定したクラス抽出が可能になります。
背景
v4.1.14でMarkdownディレクティブ構文(:span[Some Text]{.text-gray-500})のクラス抽出をサポートする修正が行われましたが、この修正がMDXファイルに副作用をもたらしていました。ディレクティブ構文内の . を空白に置換する前処理が、MDXのJSX構文内のクラス名にも適用されてしまったためです。
Tailwind CSSのドキュメントサイト自体が以下のような記法を使用しており、p-2.5 が正しく抽出されない状態になっていました:
<Example>
{
<div class="p-2.5"></div>
}
</Example>
ディレクティブ構文のサポートとMDXの互換性を両立させる必要がありました。
技術的な変更
crates/oxide/src/extractor/pre_processors/markdown.rs に括弧のネストレベルを追跡する機構が追加されました。
変更前:
let mut in_directive = false;
while let Some(_) = cursor.next() {
match (in_directive, cursor.curr()) {
(false, b'{') => {
result[cursor.pos] = b' ';
in_directive = true;
}
(true, b'}') => {
result[cursor.pos] = b' ';
in_directive = false;
}
(true, b'.') => {
result[cursor.pos] = b' ';
}
_ => {}
}
}
変更後:
let mut in_directive = false;
let mut bracket_stack = vec![];
while let Some(_) = cursor.next() {
match (in_directive, cursor.curr()) {
(false, b'{') => {
result[cursor.pos] = b' ';
in_directive = true;
}
(true, b'(' | b'[' | b'{' | b'<') => {
bracket_stack.push(cursor.curr());
}
(true, b')' | b']' | b'}' | b'>') if !bracket_stack.is_empty() => {
bracket_stack.pop();
}
(true, b'}') => {
result[cursor.pos] = b' ';
in_directive = false;
}
(true, b'.') if bracket_stack.is_empty() => {
result[cursor.pos] = b' ';
}
_ => {}
}
}
bracket_stack がネストレベルを管理し、<、{、[、( の出現時にスタックに追加、対応する閉じ括弧で削除します。. の置換処理は bracket_stack.is_empty() の場合のみ実行されるようになり、トップレベルのディレクティブ構文内でのみ動作します。
設計判断
実装は意図的にシンプルに保たれています。PR内で言及されている通り、括弧の順序検証、対応しない括弧、文字列内のエスケープ、コードブロックの除外といった複雑なケースには対応していません。
この判断の背景には、この不具合に関するバグレポートが確認されていないという事実があります。より堅牢な実装は複雑さとバグのリスクを増大させるため、実際に問題が報告された時点で対処する方針が採用されました。
現在の実装は、Markdownディレクティブ構文(トップレベルのクラス指定)とMDXのJSX構文(ネストしたクラス指定)という2つの主要なユースケースを満たしており、実用上十分な解決策となっています。
まとめ
本修正は、ネストレベルの追跡という最小限の変更で、Markdownディレクティブ構文とMDXの互換性問題を解決しました。意図的にシンプルな実装を選択することで、保守性を維持しながら実用上の問題を解消しています。Tailwind CSSのドキュメントサイト自体がこの構文に依存しているため、この修正は公式ドキュメントの品質向上にも直結します。