読者です 読者をやめる 読者になる 読者になる

YACP: よりオブジェクト指向なCSS設計のためのプリプロセッサー

この投稿はCSS Architecture Advent Calendar 2014の2日目の記事です。

よりオブジェクト指向CSSの記述を助ける、YACPというCSSプリプロセッサーを作っています。具体的な、セレクタ命名規則やディレクトリ構成の話ではないです。

Object Oriented CSS

数あるCSSの設計手法のベースとなる、OOCSS (Object Oriented CSS、オブジェクト指向CSS)というものがある。OOCSSはその名の通り、CSSのクラス設計(ルールセットの定義)にオブジェクト指向プログラミングの考え方を少し取り入れたようなものだ。

OOCSSの原則として、「構造と見た目の分離」、「コンテナとコンテンツの分離」というものがある。OOCSSが提唱していることは要するに、HTMLの構造に依存しないセレクタを書き、レイアウトと見た目に関するルールセットは別に定義しましょう、ということだ(と思っている)。そしてSMACSSMCSS等の設計手法は、OOCSSを元にルールセットの命名規則、分け方を提案したものだ。

CSS Preprocessor

話は変わってCSSプリプロセッサーについて。モダンな開発をしている人なら、SassやLess、Stylusといったプリプロセッサーを使ってCSSを書いていると思う。これらのCSSプリプロセッサーはCSSの貧弱な構文を補うためのもので、例えば変数の定義や四則演算、制御構文等の機能を持つ。

僕は、既存のCSSプリプロセッサーの機能の多くは、CSSシンタックスシュガーを定義したものに過ぎず、本質的に設計を良くするためのものではないと思っている。しかし、他のルールセットを継承する機能(Sassでいうところの@extend)は良いものだ。これにより、 意味を持つ最小限のかたまりをルールセットとして定義することができる。

CSSのプロパティの定義は複数の宣言で1つの意味を持つ場合がある。例えば、コンテンツをページの最下部に固定させるための宣言はこのように2つのプロパティを要する。

.selector {
  /* 他のプロパティ */

  position: fixed;
  bottom: 0;
}

ここでSassの@extendを用いると、この部分を別のルールセットとして定義し、元のルールセットから継承できる。

.fixed-bottom {
  position: fixed;
  bottom: 0;
}

.selector {
  // 他のプロパティ

  @extend .fixed-bottom;
}

しかし、このSassの@extendにはいくつか問題点がある。例えば以下のようなもので、

.foo {
  color: red;
}

.bar {
  font-size: 12px;
  @extend: .foo;
}

.foo {
  color: blue;
}

コンパイル結果:

.foo, .bar {
  color: red;
}

.bar {
  font-size: 12px;
}

.foo, .bar {
  color: blue;
}

.foo.barで継承したけど、どこかで.fooがカスケードしたときにそれが.barにも適用されるので意図した結果にならないかもしれない。あと、プレースホルダセレクタ (%ではじまるやつ) 以外で継承元のルールセットを定義するのは良くない。

Yet Another CSS Preprocessor

上記のような既存のCSSプリプロセッサーの問題点を解決しつつ、本質的にCSSの設計を良くするプリプロセッサーとして、YACP (Yet Another CSS Preprocessor)というものを作った。YACPが持つ、既存のプリプロセッサーにはない特徴的な機能は以下の2つだ。

まず、カスケーディングを制御する構文について。僕はCSS (Cascading Style Sheets)のカスケーディングという機能が設計が破綻しやすい原因として一役買っていると思っている。

YACPでは、セレクタを丸括弧「()」で囲むとそのルールセットはカスケーディングできなくなる。

(.bind-rule) {
  padding: 15px;
  border: 1px solid #999;
}

/* Error */
.bind-rule {
  padding: 0;
}
/* Error */
(.bind-rule) {
  border: none;
}

これにより、ルールセットのカプセル化(のようなこと)が可能となり、意図しないカスケーディングを阻止することができる。

次に、安全なルールセットの継承について。YACPにもルールセット内で他のルールセットを継承する機能がある。しかし、YACPでは継承元となるルールセットはプレースホルダセレクタ%から始まるセレクタ)で定義しなければならない。YACPはプレースホルダセレクタで定義したルールセットのみ継承可能で、この継承元となるルールセットはカスケーディングできず、コンパイル時にCSSファイルに出力されない。

%btn {
  border-radius: 5px;
  color: #fff;
  padding: 6px 12px;
}

.btn-success {
  extend: %btn;
  background-color: #4dac26;
}
/* Error */
%btn {
  padding: 8px 16px;
}

CSSではpublicな(上書き可能な)ルールセットしか作れなかったが、YACPではprivateな(上書き不可能でYACPコード内でのみ参照可能な)ルールセットを定義することができる。

また、継承元となるルールセットが複数存在し、それらが共通のプロパティを持っている場合もエラーになる。

%foo {
  font-size: 16px;
  padding: 5px 10px;
}
%bar {
  color: #fff;
  font-size: 14px;
}
/* Error */
.baz {
  extend: %foo;
  extend: %bar;
}

他にもコンパイル時にオプションとしてstrictモードを指定することもできる。

strictモードではclassセレクタと擬似要素セレクタを単体での使用のみを許容する。IDセレクタや要素セレクタセレクタのネスト、!importantも禁止している。strictモードを使用するとルールセットの詳細度が均一になるので、スタイルの適用範囲を理解しやすく、拡張性の高いスタイルシートを記述できる。

最後に

CSSは記述があまりにも柔軟なので、コピペでどんどん書けるしバッドプラクティスのようなものがあって、良いCSSerはそれをノウハウとして持っていて最悪なコードを書くことはない。既存のプリプロセッサーはシンタックスが間違っていない限り何でもコンパイルしてしまうので、バッドプラクティスをプリプロセッサーが弾いてくれると良いと思って作った。

以上、よりオブジェクト指向CSS設計を可能にするプリプロセッサー、YACPの紹介でした。GitHubのStarしてもらえると泣いて喜びます!

あわせて読みたい

僕が過去に書いた記事ですが。YACPを作り始めたときに考えていたことや実装について書いていたりします。


次回はcancerさんです。よろしくお願いします。