l12a

白ウサギを追え

stylelint-config-standard における no-descending-specificity ルール

CSS の linter として stylelint を採用し、ルールセットは stylelint-config-standard にしました。 初期導入時、僕のやり方と競合した no-descending-specificity ルールを結局無効化せずに自身を曲げた ことについて書きます。

no-descending-specificity ルールが自身のコーディングスタイルと合わない・なんとなくイヤだと思っている方に読んでもらえると良いかと思います。

stylelint-config-standard とは

stylelint 本家で配布している設定です。

github.com

no-descending-specificity ルール

no-descending-specificity · stylelint

no-descending-specificity ルールは、セレクタ記述順序に関する制約です。
これを有効にすると、
「詳細度が高いセレクタを後に書かなければならない」
という制約を設けることができます。

詳細度が高いセレクタを後に書く理由

さて詳細度が高いセレクタを後に書かなければならない理由を、CSS の基本にも触れつつ丁寧に見ていきます。
詳細度が高いセレクタを後に書いた方がいいことが感覚的にわかっていれば読み飛ばして構いません。

原則の「後勝ち」

CSS ルールセットの原則は「後勝ち」で、同じセレクタを書けば後に書いた方が優先されます。

.foo { color: red; }
.foo { color: blue; }

上記の例では .foo の文字色は blue になります。

セレクタ詳細度

ただし、セレクタには「詳細度」という重みの概念があります。
ある要素が、定義された複数のルールセットのセレクタに当てはまるとき、適用するルールセットを決定するための基準になるものが詳細度です。

#bar .foo { color: red; }
.foo { color: blue; }

上記の場合は #bar .foo の方が詳細度が高いく「先勝ち」となり文字色は red になります。

いつも「後勝ち」にするには

このように「後勝ち」になるか「先勝ち」になるかはセレクタの書き方に依存してしまいますが、
セレクタ詳細度が低い順にルールセットを書くことができれば、常に「後勝ち」になり、理解しやすくなります。

これが、詳細度が高いセレクタを後に書いた方がいい理由です。

no-descending-specificity ルールによる警告

本題へ戻ります。
stylelint-config-standard で lint をかけて no-descending-specificity ルールによる警告が出た箇所はどんなコードだったのかを擬似コードで見ます。

/* コンパイル前のSass */
.parent {
  border: solid 1px gray;
  &:hover {
    background-color: gray;
    .child {
      color: red;
    }
  }
}

.child {
  color: blue;
}

外側の要素 .parent にマウスカーソルが乗っているとき、内側の .child の文字色を変えるスタイル定義です。

Sass から CSS にコンパイルするとこうなります。

/* コンパイル後のCSS */
.parent {
  border: solid 1px gray;
}
.parent:hover {
  background-color: gray;
}
.parent:hover .child { /* 詳細度・高い */
  color: red;
}

.child { /* 詳細度・低い */
  color: blue;
}

「先勝ち」になる箇所が出ました。
先の Sass のコードはセレクタのネストや & を使って文字数を省略して簡潔で「読みやすい」と感じたので、コーディングスタイルを貫くために、
no-descending-specificity ルールを無効にすることを考え始めました。

しかし考えてみると感じていた「読みやすさ」は、

  • シングルクラスにする
  • クラスセレクタのみを使う
    • 擬似要素・擬似クラスはネストして良い
    • 親要素に依存するスタイルを書く場合は良いネストして良い
    • Modifier (BEM) の場合はネストして良い

などの規約を前提としていることに気がつきました。
そしてこの規約は複雑で、曖昧さがあり、経験則に基づいている部分が大きいことも分かりました。

そこで、no-descending-specificity ルールを stylelint-config-standard に従うことにしたのです。

コーディングスタイルの修正と脱プリプロセッサの可能性

コードは以下のように修正しました。

/* コンパイル前のSass */
.parent {
  border: solid 1px gray;
  &:hover {
    background-color: gray;
  }
}

.child {
  color: blue;
}

.parent:hover .child {
  color: red;
}

冗長になったようにも見えますが、規約としてはより単純になりました。
さらにこの変更を行ったことによって、 sass とコンパイル後の CSS のコード差分が小さくなったこともメリットでした。

/* コンパイル後のCSS */
.parent {
  border: solid 1px gray;
}
.parent:hover {
  background-color: gray;
}

.child {
  color: blue;
}

.parent:hover .child {
  color: red;
}

ここまでくれば、もうネスト記法も & も使わなくても良い気がしてきます。

おわり

stylelint-config-standard における no-descending-specificity ルールと向き合った結果、
コーディングスタイルを曲げた記録でした。 その結果として、図らずもプリプロセッサへの依存すらなくせる可能性が見えてきたのは意外な収穫です。