<!DOCTYPE LNLY>

白ウサギを追え

stylelint で no-descending-specificity ルールをどうするか。

stylelint のルールの中で唯一これまでのスタイルと合わなかった no-descending-specificity ルールをどうするか悩んで、最後には自分を曲げました。

stylelint config

stylelint で使っているルールは

github.com

これで、stylelint 本家で配布しているものです。
まず知れたルールにしておけば学習コストが下がります。

加えて、コーディング規約というのは結局のところ主観・好みによるところが大きいです。
確かに「こうだからこうがいい」という理由付けは色々とありますが、
その規約じゃなければどれくらい困るかといえば妥協可能なところが多いと思います。
メンバーの誰かが決めたルールでは、「好み」が合わないメンバーがいた時、平行線になりやすい不毛な議論に時間を割かれることもあります。
採用基準を明確にしやすいという意味でもこういうものを選んだ方が良いです。

わたしたちのチームでは「最もスターが付いたルール」を採用することに決めた。

みたいな。
どっちみち「好み」のレベルなので、矯正すればいいのです。

no-descending-specificity ルール

唯一これまで僕が書きがちなスタイルと乖離が大きかったのが no-descending-specificity ルールで、 「セレクタ詳細度が高いものを後に書かなければならない」というものです。

なぜ詳細度が高いものを後に書かなければならないのか

これはstylelintドキュメントを咀嚼したものです。

CSS は基本的に、後勝ち。同じセレクタを書けば、後に書いた方が優先されます。

/* 後に書いた方が勝つので「あお文字」 */
.hoge { color: red; }
.hoge { color: blue; }

一方、セレクタには詳細度があり、詳細度が高い場合は先に書いてあっても勝ちます。

/* 詳細度が高いと、先に書いてあっても勝つので「あか文字」 */
#fuga .hoge { color: red; }
.hoge { color: blue; }

「後勝ち」になるか「先勝ち」になるかは場合による、という条件は複雑で難しいです。
だから、詳細度が低い順に書くと、「常に後がち」となって可読性が上がります。

ルールに反していたコードとその理由

今回 stylelint をかけたプロジェクトではこのルールに指摘されたところが約20箇所ありました。具体的には、

<style lang="scss">
.this-is-parent-element {
  display: block;
  &:hover {
    background-color: gray;
    .child-element {
      color: red;
    }
  }
}

.child-element {
  color: blue;
}
</style>

こういうコード(ニュアンスだけですが)で、親要素(ほとんどは擬似クラス)に引っ張られて子要素のスタイルが変わるところです。
& で文字数を節約し、文脈的にも読みにくくない(むしろ読みやすいぐらいに)と思っていました。
問題の箇所はここの詳細度です。

<style lang="scss">
.this-is-parent-element {
  display: block;
  &:hover {
    background-color: gray;
    .child-element { /* 先に書いてあるが詳細度が高い */
      color: red;
    }
  }
}

.child-element { /* 後に書いてあるが詳細度が低い */
  color: blue;
}
</style>

これは CSS に展開すれば、

<style>
.this-is-parent-element {
  display: block;
}

.this-is-parent-element:hover {
  background-color: gray;
}

.this-is-parent-element:hover .child-element { /* 先に書いてあるが詳細度が高い */
  color: red;
}

.child-element { /* 後に書いてあるが詳細度が低い */
  color: blue;
}
</style>

詳細度の高いセレクタが先に書かれていますが、正直「後がちだと思ってたら詳細度が高いものが先にあってそっちが勝ってた」みたいなトラブルは起きません。

それは「シングルクラスにする」「クラスセレクタだけを使う(擬似要素や擬似クラスはオッケー)(親要素が絡むときは仕方ない)( modifier 的な時だけオッケー)」
のようなオレオレルールになっているからですが、

  • ルールが曖昧・複雑
  • その場その場の判断による
  • 経験則次第

みたいなところがあり、結局属人性がかなり高かったのです。

コードを修正する

これを

<style lang="scss">
.this-is-parent-element {
  display: block;
  &:hover {
    background-color: gray;
    .child-element { /* 先に書いてあるが詳細度が高い */
      color: red;
    }
  }
}

.child-element { /* 後に書いてあるが詳細度が低い */
  color: blue;
}
</style>

こうしました。

<style lang="scss">
.this-is-parent-element {
  display: block;
  &:hover {
    background-color: gray;
  }
}

.child-element { /* 先に書いてあり、詳細度が低い */
  color: blue;
}

.this-is-parent-element:hover .child-element { /* 後に書いてあり、詳細度が高い */
  color: red;
}
</style>

ネスト記法と & を使っていないのでやや冗長になってしまいましたが、前述の属人性の高さの方がコードベースにとっての負債になりやすいと判断しました。
親要素の状態に応じてスタイルを変えたい場所というのはそもそもそれほど多くはないし、今ところはこのルールにしようと思っています。

そんな感じ。

Vue.js + Scss + Webpack + IntelliJ 環境で CSS を Lint しながらコードを書く

stylelint できるように環境を整えました。

なるべく薄く設定していきたいです。

この手の作業は、適用前のコードの状態によっては、変更差分がものすごい出るので、
なんかのついでにやらないでブランチを分けて作業することをお勧めしたいです。

各モジュールを追加する。

local にモジュールをインストールします。

$ yarn add -D stylelint stylelint-config-standard stylelint-scss stylelint-webpack-plugin

最終的に入れたのは、

stylelint

www.npmjs.com

これが本体です。

stylelint-scss

www.npmjs.com

scss 用の拡張です。これがないと、 @extend などの scss 構文を解釈できません。

stylelint-config-standard

www.npmjs.com

stylelint 本家による、標準ルール設定です。自分でゼロから設定してもいいけど、要されているものがあればそれを適用します。
設定時に楽であることよりも「ぼくがかんがえたさいきょうのこんふぃぐ」をチームメイト全員にに読んで理解してもらったり
個人の主観にしか過ぎないもので行う議論にかける工数を省略したいのがモチベーションです。
他にも、 stylelint-config-recommended とかもあります。

stylelint-webpack-plugin

www.npmjs.com

webpack 用の拡張です。Stylelint を Webpack で使うために必要です。

そんな感じ。

設定ファイルを書く

stylelint の設定ファイルを書きます。特に理由がなければルートディレクトリに .stylelintrc を作って、

{
  "plugins": ["stylelint-scss"],
  "extends": "stylelint-config-standard",
  "rules": {
    "at-rule-no-unknown": null,
    "scss/at-rule-no-unknown": true
  }
}

設定を書きます。 "at-rule-no-unknown": null"scss/at-rule-no-unknown": true はオリジナルルール設定ではなくて、
plugin で導入している stylelint-scss による、 scss 独自の構文( @extend などがそうです)に対応させるための記述です。

webpack config に組み込む

webpack の設定ファイルを開き、

import StyleLintPlugin from 'stylelint-webpack-plugin'

module.exports = {
  (中略)
  plugins: [
    new StyleLintPlugin({
      files: ['**/*.{vue,css,scss}'],
    })
  ],
  (中略)
}

plugin を読み込みます。
パラメータで渡しているのは拡張子ですのでプロジェクトに合わせます。
ここまででコードのビルド工程に stylelint がかかるようになります。

あとは、僕は IntelliJ で開発しているので、最適化していきます。

IntelliJプラグイン導入

IntelliJ にはこれを使いました。

github.com

そもそもあまり選択肢がなく、検索してもこれぐらいしか出て来ません。
インストールしたら、環境設定から Languages & Flameworks > Stylesheets > Stylelint と辿り、
Enable にチェックをしておきます。なぜか最初は有効になっていませんでした。

stylelint の実行と修正

webpack でビルドするときに怒られたい

ここまでの設定で、エラーがあれば webpack のビルドは通らなくなっています。
エラー箇所もターミナルに表示されるので、適宜修正します。

リアルタイムにコードをチェックして欲しい

IntelliJ ならここまでの設定で他の linter と同様に怒ってくれます。

個別のファイルを検査したい

特定のファイルを調べたければターミナルで、

$ stylelint ./path/to/your/target.vue

このようにファイルパスを渡して実行します。網羅的にやるには

$ stylelint "./**/*.vue"

このように馴染みの記法が使えます。

IntelliJ の機能で検査したい

Command + Shift + Aキー を押してアクション検索を出し、 Inspect Code... を選択します。
任意のスコープを設定してコード解析を実行すれば、 stylelint の結果が表示されます。
ターミナルでコマンドで実行したり、 webpack のビルド過程で行われる全検査に対する利点は

  • 出力がターミナルではなく GUI なので該当箇所へのジャンプやコンテキストメニューの使用が便利
  • 件数などのサマリが出る

ことです。

引っかかった項目を修正したい

これはターミナルでやります。

$ stylelint --fix ./path/to/your/target.vue

これか、

$ stylelint --fix "./**/*.vue"

これで、ファイル個別に修正するか、網羅的にやるかを選択します。

例えば、インデントの調整や空行を入れるルールなど、ルールセットの挙動に影響がない部分に関してはこれで1発で修正がかかります。
逆に、記載順序の違いに対する指摘など、 CSSセレクタアルゴリズムやカスケーディングによって結果が変わりうるものに関しては、自動修正されません。
まず自動修正でざっと直してから、特殊なものは影響範囲をよく吟味して手動で修正していくのが良いでしょう。

そんな感じ。