三年間運用しているサービスにFLOCSSを導入してCSSの健全化を目指す

こんにちは、キッズリー開発グループ サーバサイドエンジニアの @t_uyama です。

本記事では、ローンチから三年間運用されているwebサービスにFLOCSSを導入して既存のCSSをリファクタリングしたという事例をご紹介します。また、各人で解釈が分かれがちなFLOCSSのレイヤ分けの一例と、FLOCSS導入後のCSSの開発体制についても併せてご紹介します。

FLOCSSとは?

CSSを書く際のファイル構成や命名規則などを取り決めたルールの1つです。CSSの命名規則というとBEMOOCSS ( Object Oriented CSS )、といったものが有名なところして挙げられますが、FLOCSSはこれらの良いところを組み合わせたものとなります。Foundation Layout Objectと役割ごとにレイヤ(層)が分かれているのが特徴です。これにより、CSSコードの再利用・拡張がしやすいように工夫されています。

なぜCSSに命名規則が必要なのか

たいていのプログラミング言語には、変数や関数に対して何かしらの命名規則があるものですが、コード全体での統一感を維持するのが主な理由となっています。同じことがCSSに対しても言えるのですが、CSSはその言語の仕様から統一感以外に下記のような課題を生みがちです。

  1. 全てのセレクタがグローバルで宣言されるため、名前の衝突が起こりやすい
    • 衝突した場合は、上書きされるのではなく衝突したもの同士がマージされるため、部分的にデザイン崩れが起こりうる
  2. 『id』『class』『HTML tag』やこれらの組み合わせなど、一つの要素にデザインを当てるだけなのに何パターンものセレクタの書き方が考えられてしまう

このような課題があることから、CSSにおける命名規則の選定は非常に重要となってくるのです。

FLOCSS導入以前の状況

キッズリーのUIの一例

殆どのページ毎に固有のSCSSファイルが作られていたため、たとえ同じデザインのコンポーネントがあったとしても重複して書かれているものが多々ありました。さらに読み込みの際は一つのファイルに結合してから読み込まれる関係から多くのスタイル定義がSCSSのネストに依存したものになっており、他の場所に影響を与えないようなコードになっていました。しかし、これによってCSSの再利用が妨げられてしまう問題がありました。

/* 
  他の場所で.btnのデザインを使用したいのに、
  .list、.itemとの依存関係を持ってしまっているために再利用が困難になっている。
*/
.list {
  .item {
    .btn {
      // ボタンのデザインが書かれている
    }
  }
}

命名規則もバラバラで統一感からは程遠く、BEMが使われている箇所もあればキャメルケースだったりチェーンケースだったりと非常に保守しにくいものとなっていました。このような状況からフロントエンドの実装に膨大な時間を要するという問題を抱えていたため、CSSのリファクタリングと命名規則の取り決めをしようという話になりました。

FLOCSS導入からリファクタリングの流れ

「他のプロジェクトでFLOCSSで書かれたCSSが比較的うまく運用できてるらしい」と言う話を聞いたのが始まりですが、FLOCSSのノウハウが無いところからこれを採用してリファクタリングに辿り着くまでの流れをご説明します。

1. まずは1ページだけFLOCSSで書いてみる

FLOCSSと言われても何も分からないところから始まったので、とりあえず雰囲気で書いてPRレビューを出していきました。お陰で「FLOCSSとはどうなのか」が感覚的には理解できたのとレビューを通じてどのようにレイヤを分けていくべきかについてある程度チーム内でイメージが描けました

2. レイヤ分けのルールを確認する

FLOCSSには『レイヤ(層)』という概念があります。下記はその内訳です。

  1. Foundation層
  2. Project層
  3. Object層
    • Component層
    • Project層
    • Utility層

特にComponent層とProject層の解釈が人によって異なりがちなので、事前にチーム内で認識合わせを行いました。また、FLOCSSのルールを事前に読み合わせておくことで前もってチーム内での疑問を解消しておくと混乱の予防が期待できます。合わせてREADME.mdやWikiなどにも文章として残しておきました。

3. 逐次FLOCSSに置き換えていく

FLOCSSに関する認識合わせが出来たところで、既存のCSSをFLOCSSで置き換えつつ新しいCSSは極力FLOCSSで書いています。実際にFLOCSSで書きすすめる中でFLOCSSの運用ルールに問題があれば逐次更新していきます。FLOCSSの運用に関する窓口を用意しておくと、何かあった時に対応しやすいと思いました。既にFLOCSSに詳しい人がいればその人が、いなければ導入を言い出した人が担当すると良いかと思われます。

FLOCSSで書き進めながら経験値あげていきましょう 💪

チームで採用したFLOCSSのルール

Foundation層

FLOCSS標準のルール通り。サービス全体で使うような設定( font-familyや変数など)に限り使うようにしています。

Layout層

ここもだいたいFLOCSSのルール通り。ヘッダやメインコンテンツ・メニュなどをどこに置くかなど、大まかな配置のためのレイヤという認識です。flexbox単体といった要素を整列させる目的だけのクラスもここに含めています。paddingも要素の配置に関わるので場合によって含めて良いことにしてます。命名はl-で始めます。

Component層

ほぼFLOCSSのルール通り。よく使うコンポーネントの土台(枠線があるということや、複数部品の相対的な関係性など)をここに書いていきます。特に理由がない限り1)大きさも含めサイズ・色にバリエーションがない時など。は色・大きさと言った具体的な設定は次のProject層に分けて書くことにしています。複数の要素からなる時に相対的は大きさや位置をem2)フォントサイズを基準とする大きさの単位など相対的な単位で設定します。

あとは役割を明記(.c-listなど)するのが目的なだけでルール自体は記載しないケースもあります。命名はc-で始めます。

Project層

ほぼFLOCSSのルール通り。具体的なデザイン(色・大きさなど)を含むルールはここに書くようにしています。どのレイヤにするべきか悩んだときは、原則ここに書くようにしています。「違ったらあとで直せば良いや」ぐらいの感覚ですね。通常はこのレイヤに落ち着く事が殆どでしょう。命名はp-で始めるようにしています。衝突を避けるため、極力具体的な名前になるようにしています。

Utility層

FLOCSSのルール通り。フォントサイズを12pxにしたいだけの時など、一つのルールしか含まないようなケースの場合にここに書くようにしています。margin-topなど要素間の間隔の設定も、ここに書いておくと何かと便利でした(marginはProject層に含めると何かと使い回しづらい気がしています)。命名はu-で始めます。

その他

同レイヤ間でのカスケーディングは認めていません。Component層をProject層でカスケーティングすることは認めています。

レイヤごとにディレクトリを分割しており、ファイルはコンポーネント単位で分けます。これは予期せぬクラス名の衝突を防ぐのが目的です。例えば.c-listobjects/components/_list.sass.p-alert-messageobjects/projects/_alert_messageといった具合です。

JavaScriptから呼び出すためのセレクタはjs-で始めるようにし、デザイン適用の用途では使いません。CSSの修正がJavaScriptにまで及ばないようにするためです。

新たにページを増やす場合には、FLOCSSで新たにCSSを追加するとは別に既存のCSSの利用も許容しています。FLOCSS化を強制しないのは開発速度を不毛に下げないためです。FLOCSS化の目的は、あくまで開発を円滑に進めるのとCSSの保守性を高めることにあり、保守性が下がらないのであれば古いCSSを使ってもいいのかなと思っています

実例で見るFLOCSS化の流れ

というアイコン付きのボタンを例に、FLOCSS化するとどう嬉しいのか見ていきましょう。下記は大まかな要件です。

  • サイズ: 高さ幅共に24px
  • アイコン: webフォントを使い、文字コード\EA55で呼び出せる
  • アイコンのフォントサイズは18px,色は#9B9B9B
  • アイコン違いの同様のボタンが存在する。アイコンのサイズと色は異なる場合もあるが、ボタンのサイズとフォントサイズはこの比率を保つものとする。
  • 背景色: #ebebeb
  • 枠線: 太さ1px#d8d8d8
  • 角丸: 3px
  • カーソルをホバーさせると透明度が0.8になる。

更に、このボタンを.itemの部品としてコンポーネントの上から10px右から10pxに設置します。悲しいことにposition: absolute;で実装しなければならないことにします。とりあえず再利用性とか何も考えずにCSSを実装してみましょう。

.item {
  // 省略
  .icon-btn {
    position: absolute;
    top: 10px;
    right: 10px;
    display: inline-block; // 当てる要素によってはなくても良いが一応
    box-sizing: border-box;
    height: 24px;
    width: 24px;
    border: solid 1px #d8d8d8;
    border-radius: 3px;
    background-color: #ebebeb;
    &:hover {
      opacity: 0.8;
    }
    &::after {
      content: '\EA55';
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      font-size: 18px;
      color: #9B9B9B;
    }
  }
}
<div class="item">
  <!-- 省略 -->
  <button class="icon-btn"></button>
</div>

特に何も考えずに実装するとこのようになるでしょう。ただし、これでは.item以外のコンポーネントでこのボタンを使い回すのが難しくなってしまいます。ボタンのデザイン定義の中に.itemの時にのみ必要となる配置の情報まで含んでしまってるからです。

.item {
  // 省略
  .icon-btn {
    // この3つの設定はボタンのデザインに関するものではなく、
    // ボタンの配置に関わるものであり、使い場所によって異なる
    position: absolute;
    top: 10px;
    right: 10px;
    // 後略
  }
}

更に他の種類のアイコンを持つボタンが現れた時にも、content: 'EA55'以外が全く同じ CSSを書くハメになってしまいます。これをFLOCSS化してみましょう。

.c-icon-btn {
  display: inline-block;
  box-sizing: border-box; // これはFoudation層に入れても良さそう
  width: 1em;
  height: 1em;
  &:hover {
    opacity: 0.8;
  }
  &::after {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 0.75em;
  }
}
.p-delete-icon-btn {
  font-size: 24px;
  border: solid 1px #d8d8d8;
  border-radius: 3px;
  background-color: #ebebeb;
  &:hover {
    opacity: 0.8;
  }
  &::after {
    content: '\EA55';
    color: #9B9B9B;
  }
}
.p-item {
  // 省略
}
.p-item__delete-btn {
  position: absolute;
  top: 10px
  right: 10px;
}
<div class="p-item">
  <!-- 省略 -->
  <button class="c-icon-btn p-delete-btn p-item__delete-btn"></button>
</div>

あくまで一例ですが、このような感じになりました。クラスを分けたことで嬉しいことがあります。まず、ボタンのデザインが独立したので.itemのコンポーネント以外でも使えるようになりました。また、アイコン違いのボタンが現れてきた時の差分も少なくなりました。例えばアイコン違いのボタンが作りたい場合には次のファイルを追加します。

.p-star-icon-btn {
  font-size: 24px;
  border: solid 1px #d8d8d8;
  border-radius: 3px;
  background-color: #ebebeb;
  &:hover {
    opacity: 0.8;
  }
  &::after {
    content: '\EA56'; // ここを変える
    color: #9B9B9B;
  }
}

大きさなども同じ場合は、更にまとめることで必要最小限の差分に抑えることができそうですね。

FLOCSS化のメリットとデメリット

個人的にFLOCSSを導入してよかったと思ったことと微妙なところをまとめます。

メリット

  • 細かくファイルを分割することで、セレクタ名が既に使われているかどうかの把握が容易になった
  • 既に定義済みのCSSの再利用が容易になったことで、フロントエンドの実装速度が上がった。
    • レイヤごとに分かれているため、検索しやすいのも良い

デメリット(困ったところ・微妙なところ)

  • レイヤ分けの判断基準が揺れそう
    • 似たような役割を持つComponent層とProject層の分け方が難しい
    • Layout層もProject層と見分けにくい場面が多々ある
  • 命名の難しさからは逃げられない

レイヤ分けが難しいという問題は抱えていますが、こればかりは決めの問題なのでそこを乗り越えられればとても良いルールだと思います。

まとめと課題

FLOCSSの導入によってCSSの使い回しが容易となり、フロントエンド実装に要するリソースを削減することが出来ました。自分がFLOCSS導入時からずっと窓口役を担当していましたが、フロントエンドは少し分かる程度だったので初期の頃はレイヤ分けに相当苦労したのを覚えています。今でも完全に理解したわけではありませんが、自分なりに「こう分けるのが良いのではないか」という水準までは何とかか辿り着きました。FLOCSSはあくまで保守性を高めるための命名規則の1つにすぎないので、本来のルールに拘るよりもチームで合意をとりつつ円滑に運用できるように解釈・ルール作りしていくのが良いと思います。

現状抱えている課題としては、Component層とProject層に何があるのか今ひとつ把握できなくなってきたので、オブジェクトを把握できる一覧が欲しいということです。また、増えてきたProject層のオブジェクトをどうまとめていくかについても悩ましいところです。今はまだFLOCSSへの移行フェーズなので、より進んだリファクタリングはこれから進めていきます。

脚注

脚注
1 大きさも含めサイズ・色にバリエーションがない時など。
2 フォントサイズを基準とする大きさの単位