スパゲッティになりにくいCSS設計

今年11月に株式会社LITALICOに入社しました @tanakashi です。
この記事は『[LITALICO Advent Calendar 2020](https://qiita.com/advent-calendar/2020/litalico)』6日目の記事になります。

前職ではスタートアップ企業でフルスタックエンジニアとして働いていました。
その際の開発は、リファクタリングもそこそこに、基本は実装ファースト。
バックエンド側はDBと紐付いていることもあり、ある程度の頻度で誰かがリファクタリングを行っていましたが、CSS部分は「表示されてればいい」という考えのもと、絡まりやすいまま実装が進んでいました。

これらをいかに解いていくか・開発スピードは落とさずに使い回しできる要素を増やし、むしろコーディング速度を速められるかを考えつつ取り組んでいた際の設計をこちらにまとめます。

# この記事の前提
- 新規ではなく、既存サービスをテコ入れしていく
- 1回作って終わりではなく、PDCAを回していくタイプのサイト向け
- （MVCフレームワークを扱っている場合は特に流用しやすいかもしれません。）
- 基本的な考え方
    - 思想：[Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/)
    - CSS構成：[FLOCSS](https://github.com/hiloki/flocss)
    - 命名規則：[BEM](http://bem.info/)

# CSSが混沌になり得る要因

今まで見たことがあるのはこんなパターン

- もとあるCSSクラスを打ち消すために階層を深く定義してしまう
    - とりあえずimportant!コースまっしぐらです
- ボタンなど使い回せるはずのパーツをそれぞれのページごとに記述する
    - 1px単位の誤差のある似たパーツがたくさん生まれます
- 競合させたくないけど、どこのファイルに記述すればいいか分からない
    - add2.cssとかのCSSが生まれやすいです

これらを回避するために、ざっくりと以下のことを行います。

- 階層は浅く定義できるようにする
- 全ページで使い回すCSS／独立したCSSを明示的に用意する
- ファイルのある程度の原則・クラスの命名規則を用意する

# レイヤー構成
| レイヤー | 説明 |
|:--|:--|
| Base | リセット・体裁の調整。 |
| Modules | 文字色・ボタン・アイコンなどの最小単位。Atomにあたる。 |
| Components | modulesより少し大きな、使い回しの効く部品。Moleculesにあたる。 |
| Layouts | 案件ごとに大きく異なる全体レイアウトのパーツ。Organismsにあたる。 |
| Pages(Projects) | ページ特有のデザイン。MVCで言うところのControllerごとに作成。 |
| Utility | 制作都合で付与したい便利クラス　※普段はbaseに入れていたりします |

Atomic Designの思想をおさえつつも、TemplatesとPagesという単語が自分の中で直感的ではなかったので、上記のように定義し直しました。

Base, Modules, Components, Layoutsは、どのページでも呼び出される影響範囲の広いクラスです。
Pages(Projects)は、上記とは逆に、そのフォルダやControllerごとに作成するため、影響範囲を考慮せず、独自にデザインできるものとします。

使い回しの効く要素を、その大きさによって分割しつつも、同時に使い回さないデザインのことも許容する構成となります。


# レイヤーごとの要素のふるまい
各レイヤーの要素を以下のように定義します。

## Modules
- 文字色・ボタン・アイコンなどの最小単位
- inline要素 or inline-block要素
- margin, paddingは指定しない
    - components, layouts, pagesで定義する
    - utilityでの付与は許容しない（無限に増えるため）
- 最初からmodulesに組み込むことは意識しない
    - components, layouts, pagesで頻出してきたらmodulesに移動させる

## Components
- modulesより少し大きな、使い回しの効く部品
    - modulesと組み合わせることもある
- block要素
- marginは指定しない
    - layouts, pagesで定義する
    - utilityで付与してもOK
- 最初からcomponentsに組み込むことは意識しない
    - layouts, pagesで頻出してきたらcomponentsに移動させる

## Layouts
- 案件ごとに大きく異なる全体レイアウトのパーツ
   - 1ページ内で、それぞれ1部品しか存在しない
   - 独立している、一番大きな要素
- block要素
- 最初からlayoutsレイヤーでデザインを組んでいく

## Pages(Projects)
- ページ特有のデザイン
    - そのページにおいて独立したデザイン
- MVCで言うところのControllerごとに作成
    - 共通・Viewごとにさらに分けて書いていく

## Utility
- 制作都合で付与したい便利クラス
- CSS 1行レベルのものを書く
- 積極的に増やさず、まずはpagesに書いて様子を見る
    - ある程度規則ができたら増やすことを許容する
    - margin, paddingは増やすときりがないのでpagesなどでの指定を推奨

# Sassのディレクトリ構成
```
├── base
│   ├── _base.scss
│   └── _reset.scss
│   └── _utility.scss(※独立させても良い)
├── modules
│   ├── _button.scss
│   ├── _input.scss
│   └── _media.scss
│   ├── _tag.scss
├── components
│   ├── _dialog.scss
│   ├── _form.scss
│   ├── _grid.scss
│   ├── _table.scss
├── layouts
│   ├── _footer.scss
│   ├── _header.scss
│   ├── _contents.scss
│   └── _sidebar.scss
└── pages
    ├── dashbord.scss
    ├── user.scss
    └── post.scss
```

base, modules, components, layouts は全ページで読み込まれることが前提となっているため、これらを統括する app.scss のようなファイルとして出力する。

```scss

// base
@import "base/_base.scss";
@import "base/_reset.scss";
@import "base/_utility.scss";

// modules
@import "modules/_button.scss";
@import "modules/_input.scss";
@import "modules/_media.scss";
@import "modules/_tag.scss";

// components
@import "components/_dialog.scss";
@import "components/_form.scss";
@import "components/_grid.scss";
@import "components/_table.scss";

// layouts
@import "layouts/_footer.scss";
@import "layouts/_header.scss";
@import "layouts/_contents.scss";
@import "layouts/_sidebar.scss";
```

pagesはそれぞれ出力をして、必要なもののみ呼び出すようにする。


# CSSクラス命名規則
命名時は、以下のことを重視します。

- 他のクラスと競合が起こらない（保守しやすい）
- クラス名から振る舞いがわかる（予測しやすい）

そのため、命名規則は主にBEMを採用しつつ、FLOCSSのようにそれぞれのレイヤーに応じた接頭語を付けています。

BEMはクラス名が冗長になりやすいという懸念点がありますが、以下の利点のもと採用しています。

- クラス名での競合を回避しやすい
- 階層を浅く持ち、詳細度を薄くすることができる
  - 階層が深くなり、最終的に important! で打ち消す事態を回避できる

## コンポーネント構成
BEMでは、以下の要素でコンポーネントが構成されます。

- Block：コンポーネントの単位となる塊
- Element：コンポーネントを構成する要素
- Modifier：コンポーネントのあしらいを変えるための定義

Block, Element, Modifier内の英単語はすべて`-`で接続（lower-kebab-cas）
要素同士を接続する際、BEMに則った記法で書きます。

```
Block__Element--Modifier
```

その記法に則って書いてみたものが以下のものになります（タブ型のメニューデザイン）。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="html,result" data-user="tanakashi" data-slug-hash="NWRNRYG" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="NWRNRYG"><span>See the Pen <a href="https://codepen.io/tanakashi/pen/NWRNRYG">NWRNRYG</a> by tanaka (<a href="https://codepen.io/tanakashi">@tanakashi</a>) on <a href="https://codepen.io">CodePen</a>.</span></p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>

基本すべてのコンポーネントはBlock+Element(+Modifier)で構成されているものとします。

ただ、Modulesレイヤーのコンポーネントのみ例外とし、Element(+Modifier)で存在することを許容します。（ふるまいの定義としてElementである意味合いを含むため）

## プレフィックス
base以外は、レイヤーの接頭語をそのままつけます（m-, c-, l-, p-, u-）
また、JavaScriptやVueを利用する際は、それぞれ以下のように振る舞います。

- js-
    - JSでのイベント起因のものに利用
    - デザイン用のクラスとは完全に分離して使う
- vue-
    - Vueのコンポーネントに利用
    - コンポーネント化ができるため、デザインとロジックを包括して使う

# CSSプロパティを書く際の注意
- CSSプロパティはABC順に記載
    - 誰でも分かる順序で書くため
- floatの使用は出来る限り避ける
    - レイアウトをいじりたい時は flex or grid を利用
- 横幅は不都合がない場合は、max-widthで可変できるようにしておく
    - スマホデザインを作る際に楽になる

# おわりに
PDCAをたくさん回そうとした際に、ある程度定義をしつつも、定義をしきらないという余白を作る必要が出てくると思います。
3回繰り返したらまとめる〜くらいのざっくりした感じだと、コーディングする側も気負わず臨みやすいです。

まだまだ更新しながらの運用になるため、もっとこうした方が分かりやすい！効率的！といった情報がありましたらお待ちしております🙇‍♀️🙇‍♀️

明日は @cumet04 さんの記事になります！