CSS ガバナンス
Tabula の局所性に関する主張――要素の見た目はそれ自身のマークアップから決定できる――は、すべてクラス属性についてのものです。スタイルシートは、それとは違う方法で要素に届きます:セレクタによってです。そのため、プロジェクト自身の .css ファイルは、モデル全体がたった1行で無効化されうる唯一の面であり、このレイヤーが存在するまではシステム内のどのコマンドも、それを開くことはありませんでした。
このページでは、現在それらを何が統治しているか、何をまだカバーしていないか、そして2つのゲートがどこでずれうるかを説明します。
5つのルール
両方のゲートは同じコードを実行します――packages/stylelint-plugin/src/kernel.ts にあるルールカーネルです。stylelint プラグインはエディタ向けにそれをラップし、tabula check:css は CI 向けにファイルをそこへ流し込みます。もし2度書かれていれば、両者はずれてしまい、そのずれは重要な方向で静かに起こったはずです:あなたはエディタが指摘したものを直すが、CI は別のものを指摘し、誰のエディタも言及しないルールは、ルールでなくなってしまいます。
| Code | stylelint rule | Forbidden | Why |
|---|---|---|---|
TAB-E221 | tabula/no-apply | @apply | クラスの一覧を手書きのセレクタへと合成してしまう――このプロファイルが取り除こうとしている、まさにその間接参照。Tailwind の作者自身によって否定されている(draft-c §3.2 ✚16)。 |
TAB-E222 | tabula/no-raw-rules | 承認されたエントリの外にある、プロジェクト CSS 内の手書きルール全般 | .card .title { color: red } はセレクタによってスタイリングするため、要素はそれ自身のマークアップから読み取れなくなる(draft-c §3.2 ✚18)。 |
TAB-E223 | tabula/no-scoped-custom-property | :root/html 以外で定義された --tb-* / --d-* | T17 ――このシステムで最も重要なチェック(SPEC J6)。トークンが <div> 上で再定義されうる瞬間、それを使うすべての子孫要素は、そのマークアップから読み取れなくなる。 |
TAB-E224 | tabula/no-theme-inline | @theme inline | T20 ――inline はビルド時にトークンの値をユーティリティへ代入し、条件付きのトークンをすべてコンパイルで消し去ってしまう。ダークモードは診断メッセージも出ないまま動かなくなる。これは shadcn エコシステムのデフォルトであるため、そのエコシステムで訓練されたエージェントは、これに手を伸ばしてしまう。 |
TAB-E225 | tabula/no-important-css | プロジェクト CSS 内の !important | すべてのユーティリティは詳細度 (0,1,0) に正規化されており、rank だけが衝突を決定する。たった1つの !important が、宣言をその証明の外に置いてしまう。 |
したがって、プロジェクトの .css ファイルが含んでよいのは、@import、@source、@utility、@custom-variant、@charset、本体を持たない @layer a, b; の順序宣言、そしてコメントです。それ以外は一切含めません。
2つのゲート
CI / ビルド時
tabula check:css # every project .css file + the emitted stylesheets
tabula build --check # drift + scan + check:css, the one command CI runs
tabula build --check --no-css # drift + scan only; the CSS gate must be opted OUT of
tabula check:css --css-entry src/theme/entry.css # designate a non-conventional entryエディタ上 ――.stylelintrc.json。このリポジトリ自身の設定を反映したものです:
{
"plugins": ["@tabula-css/stylelint-plugin"],
"rules": {
"tabula/no-apply": true,
"tabula/no-raw-rules": true,
"tabula/no-scoped-custom-property": true,
"tabula/no-theme-inline": true,
"tabula/no-important-css": true
},
"overrides": [
{ "files": ["src/app.css"], "rules": { "tabula/no-raw-rules": [true, { "sanctionedEntry": true }] } }
]
}@tabula-css/stylelint-plugin/config はルールのブロックをエクスポートしているため、コピーする代わりにスプレッドで展開できます。stylelint はオプションのピア依存です:プラグインのカーネルは、そのインポートグラフの中に stylelint を持ちません。これによって、CLI は執筆ツールをランタイム依存として抱えることなく、それを利用できます。
承認されたエントリ
すべてのアプリは、@import "../.tabula/source.css"; と、アプリ自身のルートレベルのカスタムプロパティを保持する1つのスタイルシートを必要とします。そのファイル――そしてそのファイルだけ――が TAB-E222 から免除されます。
それ以外の4つからは免除されません。エントリ内の :root ブロックは問題ありませんが、エントリ内の --tb-color-accent: red は、どこに現れようと TAB-E223 になります。実例については examples/reference-ui/src/app.css を参照してください。
あるファイルがエントリになる方法:
- 慣例によって――
src/app.css、src/index.css、src/styles.css、app/globals.css、その他いくつか(packages/cli/src/commands/check-css.tsのDEFAULT_CSS_ENTRIES); - あるいは明示的に――
tabula check:css --css-entry <path>。繰り返し指定可能です。
2つのゲートがずれうる唯一の箇所は、まさにこのリストです:check:css はフラグと慣例からそれを読み取り、stylelint はあなたの .stylelintrc の overrides ブロックからそれを読み取ります。両者を一致させてください。もし食い違えば、出荷するかどうかを決めるのは CI のゲートの方です。
記録された決定事項と不整合
1. Draft C のコードは尊重されていません。 Draft C はこれらの不変条件に PLANAR-E210(T17)、PLANAR-E211(T20)、PLANAR-E212(✚18)を割り当てています。しかし、この3つの番号は、CSS レイヤーが書かれるより前に、このカタログの中でビルドの不変条件 I1/I2/I3 にすでに使われていました。2つの意味を持つコードは、誰も認識しないコードよりも悪いものです――tabula explain TAB-E212 は1つの説明文しか表示できず、読み手は手書きの .card p を探しているのに rank の衝突を追いかける羽目になってしまいます。代わりにこのファミリーは E221〜E225 として新たに割り当てられ、各カタログのエントリには draft との対応関係が記録されています。
2. T17 は、生成されたスタイルシートに対しては異なる読み方をされ、そうならざるを得ません。 書かれている通りの T17 は、プロファイルのカスタムプロパティはすべて、:root でのみ定義されうると言っています。プロファイル自身の出力は、これを意図的に破っています:.ring-accent { --tb-ring-color: … } や .shadow-sm { --tb-shadow: … } は、要素ごとの合成 (composition) プロパティであり、初期値を伴って inherits: false として宣言されています。そのため:
- プロジェクト CSS ――ルート主体の外にある
--tb-*/--d-*の定義はすべて違反であり、例外はありません。プロジェクト CSS がそもそもプロファイルのプロパティを書く正当な理由はありません。要素ごとの値についてはdyn()が承認されたチャネルであり、それはスタイルシートではなくインラインスタイルを書き込みます。 - 生成された CSS ――ルート主体の外にある定義が違反となるのは、生成されたセット内のどこかでルート主体において実際に定義されている、つまりテーマトークンであるプロパティについてだけです。これによって、プロファイル自身の正しい出力を失敗させることなく、T17 が捕捉しようとしていること(ルートより下で再定義されたトークン)を捕捉できます。
SPEC は本来、後者を述べるべきです。しかし実際には前者を述べています。チェックを弱めることで解決するのではなく、ここに記録しておきます。
3. @utility は要素バインディングです。 @utility card-shell { --tb-color-accent: red } は、それを付与するすべての要素上でテーマトークンを再定義するクラスにコンパイルされます――プロジェクト CSS が含むことを許されている、その唯一の at-rule をまとった、同じ T17 の破りです。カーネルはこの理由から、@utility 内の宣言を要素スコープとして扱います。
4. エントリの一覧は本来 tabula.config.json に属します。 その永続的な置き場所は、設定スキーマ(packages/core/src/schemas/config.ts)の scan.sources の隣にある css.entries キーであるべきです。このスキーマは additionalProperties: false であるため、認識されない css キーは無視されるのではなく、検証エラーになります。このレイヤーを構築したタスクはそのファイルを所有していなかったため、現状ではこの一覧は慣例と --css-entry の組み合わせです。それを移動するのは1つのキーの変更で済み、上で述べたずれの箇所を取り除くことになります。
5. パースできないスタイルシートは、スキップではなく検出結果です。 PostCSS がパースできない .css ファイルは TAB-E222 として報告されます。これをスキップしてしまうと、「構文的に壊れている」ことが、このページのすべてのルールをすり抜ける最も安上がりな方法になってしまいます。
このレイヤーがまだカバーしていないこと
- T18 ――ルートより下にある軸属性。
<div data-theme="dark">は不活性です(生成されるセレクタは:root[data-theme=…]なので、その属性には一切効果がありません)。ESLint ルールtabula/no-axis-attribute-below-rootは、それを著者に説明するためのものです。CSS では、.panel[data-theme="dark"]セレクタは軸違反としてではなく、手書きルール(TAB-E222)として捕捉されます――誤った名前の下での正しい結果です。 - プロジェクトが記述した
@source行――閉包性の穴は、まだ開いたままです。 あなたのスタイルシートの1つに@source "./src"を書くと、Tailwind のソーススキャンが再び有効になり、テーマ名前空間はすでに定義されているため、bg-red-500やp-4が再び生成され始めます。check:cssはこれをフラグしません。draft-c §3.2 ✚18 が、プロジェクト CSS が含んでよい at-rule の中に@sourceを挙げているからです。そのリストと、レビュー A7 における閉包性の議論は互いに矛盾しており、このレイヤーはそのリストの方を実装しています。SPEC でそれが解決されるまでは:.tabula/source.cssの@importを、プロジェクト内で唯一の Tailwind エントリとして保ち、@source行は一切追加しないでください。grep -rn '@source' srcがそのチェックです。 - インポートされたサードパーティの CSS。 このゲートはプロジェクトのツリーを走査しますが、
node_modulesは走査しません。あなたがインポートするベンダー提供のスタイルシートは、以前とまったく同じようにレビューされないままです。 - 実行時の
<style>インジェクション。 コンポーネントが実行時にドキュメントへ書き込むものは何であれ、ここにあるすべてのゲートの外にあります。