社内向け技術文書 ペパボのフロントエンドスタンダード CSS

CSS 設計の長い夢

社内向け技術文書 ペパボのフロントエンドスタンダード CSS

フロントエンド周りの技術は驚異的なスピードで進化し、また多様化しています。それらを全てマスターするのは途方もなく大変なので、ペパボでは、社内のエンジニア・デザイナが「最低限これだけはおさえておこう」というスタンダードを文書化することにいたしました。社内向けを想定した文書ではありますが、社内のみに留めず多くの方に役立てたいと考えたため公開します。

  1. スタイルシートの夢
    1. (1) 予測しやすい
    2. (2) 再利用しやすい
    3. (3) 保守しやすい
    4. (4) 拡張しやすい
  2. 代表的な CSS 設計手法
  3. 既存プロジェクトの CSS に立ち向かう!
    1. (0) 流れ
    2. (1) 既存の CSS ファイルを元に SCSS ファイルに変換する
    3. (2) イニシャライズ CSS や共通の箇所のスタイルを分離する
    4. (3) CSSLint を使って、修正しやすいところから整理していく
    5. (4) コンパイル
    6. (5) スタイルのスコープ(あえて)
  4. リファクタリングを行う際のポイントと FLOCSS について
    1. (1) 理由その1 カテゴライズしやすい
    2. (2) 理由その2 ルールと役割を分けて考えることができる
    3. (3) 理由その3 既存クラスとリファクタリング後のクラスを分けやすい
  5. 参考文献

スタイルシートの夢

今朝、夢を見ました。夢のなかで、僕はスタイルシートを書いていました。

そのスタイルシートは、書きやすくて、読みやすくて、壊れない。そんな「運用しやすい」CSS でした。満足げに仕事を終えたところで目が覚めると、僕の隣り(ローカル環境のリポジトリ)には、ウェブサービスの長い歴史のなかでさまざまな人が関わって育てられてきた、1万行くらいに肥大化した CSS がスヤスヤと寝息を立てて眠っているのでした。

気がつくと CSS が 1万行くらいになっているのは、なんでなんだろう?

夢で会った「運用しやすい」CSS で仕事がしたいけど「運用しやすい」ってどういうことなんだ。その手がかりとして、ここでは Philip Walton さんが 2012年に『CSS Architecture』という記事で示していた、CSS を設計するときに目指すべき 4つのゴールを紹介したいと思います。もっと詳しく知りたい方はぜひ 原文Hiroki Tani さんによる日本語訳)を参照ください!

(1) 予測しやすい

予測しやすい CSS とはルールが期待通りに振る舞うことを意味する。ルールを追加・更新したとき、そのルールが意図せずサイトの一部に影響を与えるべきではない。滅多に変更されない小規模なサイトであれば、このことはあまり重要ではないが、数十、数百ページの大規模なサイトであれば、予測しやすい CSS は必須といえる。

例えば、こんな CSS があったとします。

.widget {
  background: yellow;
  border: 1px solid black;
  color: black;
  width: 50%;
}

黄色いウィジェットが便利なので、サイドバーにも置きたくなってしまった。サイドバーに置いてみると、横幅は 200px ぐらいがいいかな…

#sidebar .widget {
  width: 200px;
}

トップページは黄色だと目立ちすぎだから、白色がいいな。

body.homepage .widget {
  background: white;
}

こうなると、もはや .widget はいったいどんな形をしているのか、誰も把握できない。サイドバーやトップページにあるとき .widget の見た目がどんな風に変身してしまうのか、予測がつかない。

(2) 再利用しやすい

CSS のルールは抽象的で、十分に分離されているべきである。それはパターンとすでに解決した問題を書きなおす必要なく、既存のパーツから新しいコンポーネントを速くつくることができるということだ。

body.homepage にある背景が白い .widget がかっこいいから body.aboutpage でも使いたいな。

body.homepage .widget,
body.aboutpage .widget {
  background: white;
}

こう書くと、使う場所が増えるごとに追記していかなくてはならなくなる。できれば、同じコードはもう書かなくていいほうが、いいよね。

(3) 保守しやすい

サイトに新しいコンポーネントと機能が追加・更新されるか、再編される必要があるとき、既存の CSS のリファクタリングを必要とすべきではない。ページにコンポーネントX を追加するときに、そのわずかな存在によってコンポーネントY を壊すべきではない。

リニューアルすることになりました、.widget の背景はピンクになります!ってなっても、対応しやすいだろうか。コピペを繰り返していたら、どうなるだろう。全部書き換えなくちゃ、漏れがあったら修正しなくちゃってなったら、どうしよう。

(4) 拡張しやすい

サイトが大きく、複雑に成長していくにつれて、通常はたくさんのデベロッパがメンテナンスのために必要となる。 拡張しやすい CSS とはひとりのデベロッパか、大きなエンジニアチームかを問わず、容易に管理できることを意味する。またそのサイトの CSS アーキテクチャに、巨大な学習曲線を必要することなく容易に近づけるという意味でもある。あなたが今日 CSS を触る唯一のデベロッパだからといって、先々にも常にあなただけであるというわけではない。

この CSS を触るのが自分じゃなかったら。自分しか触らないとしても、半年後の自分だったら。class 名が l とか r で、left とか right のことって、わかるだろうか。そもそも半年後、この要素がページの左にあったり右にあったりするだろうか。

うーん、これから僕たちが目指す「運用しやすい」CSS とはどういうものなのか、なんとなくわかってきたような気がする。

それに、つらいのは僕たちだけじゃないんだ。みんなつらい思いをしてるから、もうすでにいろんな設計手法が考えられているみたいです。

代表的な CSS 設計手法

  • この項目の担当 @otthi

ここで紹介する CSS 設計は OOCSS を代表とする多くのプロジェクトで用いられている設計手法です。 いずれの設計手法も前項の「運用しやすい」CSS を具現化したものであるという点において共通しています。

既存プロジェクトの CSS に立ち向かう!

  • この項目の担当 @otthi

さて、これまで見てきた考え方や設計手法はいわば理想的なものです。しかし、我々の眼前にはすでに 1万行くらいに肥大化してしまった CSS が立ちはだかっています(!)これまで見てきたものを既存のプロジェクトの CSS にどう適用していけばよいでしょうか。

ここでは既存のプロジェクトの肥大化した CSS を以下の点にポイントを置きつつコンポーネント化して整理する流れを考えてみます。

  • スタイルと文書構造の分離
  • SCSS 化
  • 各コンポーネントのデザイン変更を行いやすくする
  • 運用しやすい環境を作る

(0) 流れ

    1. 各 CSS ファイルを SCSS に変換する
    1. イニシャライズ CSS・レイアウト系の CSS を分離する
    1. CSSLint を使ってソースの整理する
    1. SCSS ファイルのコンパイル設定をする
    1. 各ページの CSS を個別に整理する

(1) 既存の CSS ファイルを元に SCSS ファイルに変換する

例えば、既存の CSS ファイルが old_version.css だとしたら old_version.scss に変換し、特定のディレクトリに置きます。

└── assets
    └── scss
        └── old_version.scss

(2) イニシャライズ CSS や共通の箇所のスタイルを分離する

old_version.scss に記述されているイニシャライズ CSS やヘッダー・フッターのスタイルを各 SCSS ファイルにまとめ直します。

└── assets
    └── scss
        ├── _base.scss   // イニシャライズ CSS
        ├── _layout.scss // ヘッダーやフッターの CSS
        ├── common.scss  // _base.scss や _layout.scss を @import する
        └── old_version.scss

(3) CSSLint を使って、修正しやすいところから整理していく

主に基本的におさえておいた方がいい項目のエラーをつぶす。

  • empty-rules
    • プロパティ指定が空のルールが存在すると警告
    • Disallow empty rules
  • known-properties
    • プロパティ名の typo が存在すると警告
    • Require use of known properties
  • display-property-grouping
    • 1つのルールに指定されているプロパティ同士が意味をなさない組み合わせが存在すると警告
    • display: inline;marginheight など
    • Require properties appropriate for display

その他にも CSSLint では色々な警告を通知してくれますが、状況や必要に応じてチェックしてみてください。

ハック

ハックを使っていると、新しいブラウザで予期しない表示になってしまうことがあるため、極力避けた方がよいです。

  • star-property-hack
    • *(スターハック)を使用していると警告
    • Disallow star hack
  • underscore-property-hack
    • _(アンダースコアハック)を使用していると警告
    • Disallow underscore hack

ルールの優先度

  • ids
  • important
    • !important を使用していると警告
    • Disallow !important

ブラウザ互換性

  • adjoining-classes
    • IE6 対象の場合
    • Disallow adjoining classes
  • non-link-hover
    • IE7 対象の場合
    • Avoid un-anchored hovers
  • box-sizing
    • IE6/7 対象の場合
    • Disallow box-sizing

(4) コンパイル

compass などで SCSS ファイルのコンパイルの設定を行います。

├── assets   // コンパイル前のファイルを置くディレクトリ
│   └── scss
│       ├── _base.scss
│       ├── _layout.scss
│       ├── common.scss
│       └── old_version.scss
└── public  // 公開用ファイルを置くディレクトリ
    └── css // コンパイル後のCSSファイルを置くディレクトリ
        ├── common.css
        └── old_version.css

(5) スタイルのスコープ(あえて)

  • old_version.scss 内に特定のページでしか使われていないスタイルが記述されている場合は、あえてスタイルをスコープして分離します。
  • scope-- など、プレフィックスをつけて既存の idclass と競合しないようにします。
  • 注意: スコープするルールが別のページで使用されている可能性がないか、grep などして確認してください。

page_A.html

<div class="scope--page-a">
  ...(ヘッダー・フッターなどを除いたpage_A.htmlのコンテンツ)
</div>

page_a.scss

.scope--page-a {
  ... /* page_A.html用のルールを記述 */
}
├── assets
│   └── scss
│       ├── _base.scss
│       ├── _layout.scss
│       ├── common.scss
│       ├── old_version.scss
│       └── pages
│           ├── page_a.scss
│           └── page_b.scss
└── public
    ├── css
    ├── page_A.html
    └── page_B.html

リファクタリングを行う際のポイントと FLOCSS について

リファクタリングを行う際のポイントはゴールになる全体像をイメージしておくことだと思います。「どの CSS 設計手法をベースとして用いるか」「どの部分のルールを参考にするか」など、プロジェクトの内容や規模によってリファクタリングしやすい設計ルール(全体像)を決めましょう。

なお、筆者は代表的な CSS 設計手法のうち、FLOCSS の考え方をリファクタリングの際に取り入れることがしばしばあります。FLOCSS をベースにする理由を紹介したいと思います。

(1) 理由その1 カテゴライズしやすい

既存の CSS でも、いくつかのカテゴリーに分けられているかと思います。FLOCSS の CSS 設計にある「レイヤー」はそのカテゴリー分けに近い考え方になので置き換えやすくなっています。

FLOCSS 既存 CSS
Foundation イニシャライズ CSS
Layout ヘッダー・フッターなどのレイアウト部分
Object その他メインコンテンツの各部品など

(2) 理由その2 ルールと役割を分けて考えることができる

既存の CSS では「その他メインコンテンツの各部品など」の部分がカオスになっていることが多いですが、FLOCSS では「Object」を更に 3つのカテゴリーに分けていることにより、リファクタリングを進めていく中で役割をはっきり切り分けることができます。

  1. Component
  2. Project
  3. Utility

ref: FLOCSS

Component とは

  • 一般的によく使われるパターン
  • button など
  • 出来る限り、最低限の機能を持ったものとして定義

Project とは

  • プロジェクト固有のパターン
  • いくつかの Component と、それに該当しない要素によって構成されるものを定義
  • 例えば、記事一覧や、ユーザープロフィール、画像ギャラリーなどコンテンツを構成する要素

Utility とは

  • Component と Project レイヤーの Object のモディファイアで解決することが難しい・適切では無い、わずかなスタイルの調整のための便利クラスなどを定義
  • Component、Project レイヤーの Object を無尽蔵に増やしてしまうことを防いだり、またこれらの Object 自体が持つべきではない margin の代わりに .mbs { margin-bottom: 10px; } のような Utility Object を用いて、隣接するモジュールとの間隔をつくるといった役割
  • clearfix テクニックのためのルールセットが定義されているヘルパークラス

(3) 理由その3 既存クラスとリファクタリング後のクラスを分けやすい

FLOCSS では Object に含まれる Component・Project・Utility のクラス名には、それぞれプレフィックスをつけるルールになっています。また MindBEMding の命名規則を取り入れています。

既存プロジェクトのコードにもよりますが、プレフィックスや独特の命名規則によって、要素に指定されているクラス名がリファクタリング前・後のどちらのクラスかがパッと見で判断できます。

レイヤー プレフィックス
Component .c-**
Project .p-**
Utility .u-**
├── assets
│   └── scss
│       ├── foundation
│       │   ├── _normalize.scss    // イニシャライズ CSS
│       │   ├── _typography.scss   // フォント
│       │   └── _base.scss         // 背景
│       ├── layout
│       │   ├── _header.scss
│       │   ├── _sidebar.scss
│       │   └── _footer.scss
│       ├── object
│       │   ├── compornent
│       │   │   ├── _button.scss
│       │   │   ├── _grid.scss
│       │   │   ├── _alert.scss
│       │   │   └── _icon.scss
│       │   ├── project
│       │   │   ├── _article.scss  // コンテンツ・記事
│       │   │   └── _product.scss  // 商品・ギャラリー
│       │   └── utility
│       │       ├── _align.scss    // 例外的に行うデザイン調整
│       │       └── _helper.scss   // ヘルパー class
│       ├── _settings.scss
│       ├── common.scss
│       ├── old_version.scss
│       └── pages
│           ├── page_a.scss
│           └── page_b.scss
└── public
    ├── css
    ├── page_A.html
    └── page_B.html

ハッ!

なんだ、夢か…。でも、がんばろう。

参考文献