READYFOR Tech Blog

READYFOR のエンジニアブログ

RailsアプリケーションのカオスなCSSに規約をゆるく導入する 〜要素セレクタ警察〜

はじめに

こんにちは!READYFORでフロントエンドエンジニアをしております、江面( @neripark )と申します。 最近は主に Next.js (TypeScript)を用いたSPA開発に従事しております。CSS設計、コンポーネント設計、Netlify などが好きです!

本日はタイトルにもある通り、既存のRailsアプリケーションにCSSコーディング規約を導入したお話をしようと思います! 既存のプロダクトのCSSで苦労している方の参考になれば幸いです。

これまでのREADYFORのCSS

READYFOR は2011年にスタートした、日本初のクラウドファンディングサービスです。 その中身は Ruby on Rails で組まれた標準的なモノリシックWebアプリケーションで、サービスの成長とともに、機能開発も重ねられていきました。

その流れの中で、いろいろなエンジニアがいろいろな文脈で新規開発・修正・リファクタリングを行った結果、僕が入社した2018年には、(特にフロントエンドは)全体像を把握するのがとても難しい状態になっていました。

CSSにフォーカスすると、

  1. 書き方がばらばらで、まず影響範囲がわからない
  2. 既存のスタイルが干渉してくることもある
  3. 干渉元は影響範囲が広すぎて手を加えられない(加えている時間がない)
  4. 手元でプロパティの上書きや、詳細度で勝つためのセレクタを書いて乗り切る
  5. 根本的な改善がまた少し大変になる(1に戻る)

という、THE・ツラいCSSを地で行く感じでした。

(しかし、READYFORのCSSには !important がほとんど使われておらず、そこは救いでした。守ってきてくださった先人のエンジニアのみなさんに感謝...)

すでに稼働しているプロダクトに新しい設計や技術を導入する場合、「少しずつ入れていく」のはセオリーの1つではありますが、重要度や優先度の関係で最後までやりきれず、さまざまな文脈のコードが混在した状態となってしまうのも、仕方がないことだと考えています。

先人のエンジニアのみなさんの苦労や、最後までやりきれなかった悔しさが、コードからひしひしと伝わってきます...。

(実際、BEMを導入しようとした形跡があったり、ボイラープレート的なCSSがモジュールとして切り出されていたりと、試行錯誤の跡はすごくたくさん見つかりました...😭 )

考えたこと

このようなツラい状況の中、大規模なリファクタリングや、根本的な設計をし直す時間は取れない...

そこで何をしたらいいのか?を考えた結果、まずはこれ以上負債を増やさないようにする ことが第一歩だと考えました。

抜本的な改善(特にCSS)はしんどいしリスクも大きい、しかしリターンは見えにくい。

それならば、抜本的な改善は(とりあえず)諦め、メンバーが気負うことなくゆるく守ることでこれ以上負債が増えなくなるのであれば、第一歩としては上出来じゃないか。

そんな規約を目指し、社内のKibela に、書きなぐりました。

READYFOR初の、CSSコーディング規約を作った

CSSコーディング規約の冒頭
限りなくポエムに近いCSSコーディング規約の冒頭

すごく、感情が先行していますね。。

ここで導入した具体的な規約は、以下のとおりです。

  1. 要素セレクタの使用禁止
  2. !important の使用禁止
  3. IDセレクタの使用禁止
  4. Scssのネストを極力減らす
  5. その他おまかせですが、基本的にクラスセレクタのみを使いましょう
  6. 命名規則はひとまず自由

「ゆるく守れること」

「これ以上負債が増えない状態を作れること」

「他に影響を与えにくく、直しやすいCSSを書けること」を、コンセプトとしています。

今回は、この中の最重要項目である「1. 要素セレクタの使用を禁止」について、詳細を述べていきます。

要素セレクタの使用を禁止

CSSの要素セレクタは、みなさんはお好きですか?便利ですよね!

要素名を書くだけで複数の要素に当てるスタイルを一気に書けるし、クラスなどの名前を考えなくて良いし、HTMLもすっきりします。

しかし、それらのメリットよりも、少なくともREADYFORのCSSコーディングにおいてはデメリットのほうが大きいと判断し、使用を禁止としました。

デメリット1:要素セレクタは後から見たときに判別しにくい

例えば .card p というセレクタで、p要素のスタイルを書いたとしましょう。 p の前に .card を書いているので、.card 配下にしか当たらないので安心です。無事、スタイルはリリースされました。

後日、HTML側に変更が入り、 .card 配下に別の種類のp要素が生まれました。 最初に書いてあったp要素へのスタイルは、後から追加されたほうのp要素には必要ありません。 このとき、あなたならどうしますか?

  1. 最初からあったp要素に fooクラスを付与し、.card .foo と変更する
  2. 既存の資産には変更を加えず、後から追加されたp要素専用の .card .bar を書き、上書きでスタイリングする
  3. .card p の後に、div.card p などを追加し、詳細度で勝つようにして、上書きでスタイリングする

どれでも乗り切れそうですし、他にもたくさんの選択肢が考えられそうですね。

しかし、最初から .card p.card .foo だったら、このようなことは考慮せずに済んだと思いませんか?

長く運用されるプロダクトでは、何回、何十回と修正が繰り返されることを想定する必要があります。

.card の配下にさらに修正が重ねられると、最初に書いてあった .card p はどの p 要素に当てたかったスタイルなのか、後から見た人が判別するのは非常に困難です。 その思考・調査コストは、気が付かないうちに少しずつ増えていきます。

クラスセレクタを使って明示的にスタイリングしてあれば、未然に防ぐことができます。

要素セレクタは、コードを書いているそのときだけは速く書けて便利ですが、長期的に見たときに思考コストがどんどん増えていくので、最初から使うべきではないと考えています。

デメリット2:要素セレクタはマークアップに依存する

後日、この .card を他のページでも再利用するためにモジュール化しようという話が上がりました。 モジュール化するなら、改めてHTMLセマンティクスを守ったものにしようと考えます。素晴らしいですね。

そこで、最初から存在していた .card pp は見出しにしたほうがよさそうだな、との結論になったとします。 ...HTMLを変更したいだけなのに、CSSも直さなければならなくなりました。

他にも、見出しレベルを変える場合や、リストの下にリストをネストする場合、div を意味のあるマークアップに変更する場合など、「HTMLを変えたいがスタイルは変えたくない場合」は多いです。 そうなってからクラスセレクタを使い始めてもよいのですが、要素セレクタ→クラスセレクタの変更の際に詳細度が変わるため、また影響を確認する必要が出てきます。

これも、最初からクラスセレクタを使ってスタイリングしておくことで、未然に防ぐことができます。 スタイリングとマークアップはなるべく分離しておいたほうが、お互いの依存が減るので保守しやすくなると思います。

運用を開始してみた

実際にこの規約を社内に展開し、運用をスタートさせます。

業務委託のメンバーや、たまにしかCSSを書かないメンバーもいるので、最初はPullRequestが上がってきた段階で、注意深くチェックをします。

来る日も... 要素セレクタ警察の取締り

来る日も... 要素セレクタ警察の取締り

来る日も...... 要素セレクタ警察の取締り

来る日も... 要素セレクタ警察の取締り

来る日も.......... 要素セレクタ警察の取締り

こうして、要素セレクタ警察(僕)が爆誕することとなりました。

運用してみてどうだったか

はぁはぁ...フロントエンドエンジニアと要素セレクタ警察の兼業は大変ですね。

しかし、治安は少しずつ良くなっている 保守性は少しずつ上がっていると感じています。

予期せぬスタイル崩れは減りましたし、どのHTML要素にどのスタイルを当てたいのかが明示的になったおかげで、修正もしやすくなりました。

今後やっていきたいこと

要素セレクタ警察の自動化 ですね。これに尽きます。

今回の規約は既存の資産全体に適用するものではなく「これからCSSを書く場合のルール」なので、全ファイルにlintを適用するのではなく、Git の Diff を見て要素セレクタが増えていないかをチェックする必要があります。

npm の lint-staged husky などを使って、 stage したファイルのみチェックするようにするのか、はたまた別のツールを使うのか...

READYFORのフロントエンドは、すでに React/TypeScript/Emotion でのコンポーネント志向に舵を切っており、既存のRails側のCSSはどうしても優先度が下がってしまいますが、折を見て実践していきたいです。

補足

この記事で主張している内容は、決して要素セレクタ自体を全否定するものではありません。

ブラウザのデフォルトスタイルを一括で制御する目的のCSSなどは、要素セレクタを使ったほうが、コードの量が少なくて済むなどのメリットは大きくなると思います。

(SMACSS や FLOCSS などの設計手法も、この考え方で組まれていますね。)

あくまで、「まとまったCSS設計がない状態で運用されてきたプロダクトに後から規約を持ち込む場合」を前提とした考えであることを、ご承知おきいただければと思います。

最後に

いかがでしたでしょうか。

既存のプロダクトのCSSがカオスでお困りでしたら、「ゆるく実践できる規約を作ってみる」「まずはセレクタの規約から始める(特に要素セレクタ)」はおすすめできるかもしれません。

参考になれば幸いです!