Sass で Singleton を実現し、安心してクラスを再利用する

この article は Less & Sass Advent Calendar の23日目のエントリーとして書かれています(あれれ?!)。

概要

CSSメタ言語を導入する目的の一つにリソース再利用性の実現というものがある。Sass の場合、mixin や extend によって実現している。特に extend によるスタイルの継承は強力である。

しかし再利用できるようなクラスをまとめたライブラリを import して利用すると、実際には使われていないクラスも出力されてしまい、ファイル容量が無駄に肥大する。使うクラスだけコピーするとか、使わないクラスは削除するなどの手動管理は手間がかかる。

そこでクラス自体を出力するような mixin を作れば、必要なクラス定義だけを include でき、不要な CSS コードを出力しなくて済む。しかし、extend したいクラスを必要になる度に include してしまうと、同じクラスが複数回出力されて CSS コードが肥大する。手動で管理するのはやはり手間がかかる。

あるクラスを何回 include しても必ず一回しか出力されないことを保証する singleton のような仕組みがあればこの問題を解決できそうである。

そこでまず、あるクラスを出力するための mixin を作り、それらに対して include ステータスフラグ用のグローバル変数を用意し、!default フラグによってその状態を制御することで singleton 機構を実現した。

Sass とわたくし

こんにちは。日本初の Sass エヴァンジェリスト(自称)の kotarok です。僕が初めて Sass について人前で熱く語ったのは2010年7月24日に開催された「オレ標準 JavaScript 勉強会」というイベントで、でした。この時のプレゼンはこちらの UST に上がってます(2:50〜)ので良かったら見てみてください。僕が Sass を使い始めて4日目で人に語るという無謀を冒している姿を見ることができます。というかそもそもなんで JS 勉強会で Sass の話ししてるんだという点ですが、それも映像の中で知ることができます。チェケラッチョ!

そんでもって2回目に Sass について話した時の UST。こちらではもうちょっと詳しく Sass の説明をしています。この時に Sass エヴァンジェリストを名乗っていますね。

まあそんな訳でしばらく前から Sass には粘着しており、エヴァンジェリストを名乗ってはいたのですが、肝心な布教活動をほとんどしてなくて、そうこうする間に heil2u 先生や tarkel 先生がどんどこ blog で記事を書いてエヴァンジェられており、なんだかんだでこんな advent calendar が成立するほどになったのは喜ばしい限りです。

CSS メタ言語を利用するメリット

ではそろそろ本題に入りたいのですが、その前に CSS メタ言語を利用するメリットや目的がそもそもなんだったのか、ということをおさらいしてみたいと思います。

まず、現在求められているようなウェブサイトやウェブアプリを制作するためのスタイル記述言語として、CSS は貧弱すぎる、ということが前提として言えます。ブログ程度のサイトであればまだしも、様々なコンテンツモデルを持った企業サイトやECサイト、リッチで複雑な UI (UXとしてシンプルでもデザインルールやその実装は複雑、というケースはいくらでもありえます)を持ったウェブサービスのヴィジュアルスタイルを記述するには完全に力不足です。

何らかのルールを効率的に記述できる言語には少なからずプログラマブルな性質が要求されますが、CSS には全くそのような機能はありません。特にスタイルの定義を再利用できないというのは致命的な欠点と言えるでしょう。Sass や Less のような CSS メタ言語を使うことによってこのような弱点を補うことができます。

中でも最も重要な点は、先にも述べた「再利用」です。およそ物事を効率化するにあたって最も導入しやすく効果が大きい解決方法は「リソースの再利用」でしょう(まあ時にはその誘惑が罠となることもあるのですが)。

CSS におけるコードの再利用

CSS においてどのようにリソースを再利用するのか、という課題に対してはどの CSS メタ言語でもまず mixin が実装されています。これは、あるコード片に名前をつけて定義し、別の場所でその名前を呼び出すとそのコピーが出力される、という機能です。これは最もシンプルなコード再利用の仕組みと言え、概念としてもとてもわかり易いです。

//define mixin
@mixin boldred {
  font-weight: bold;
  color: red;
}

//use mixin
.error {
  @include boldred;
}

//output code
.error {
  font-weight: bold;
  color: red;
}

しかし、実は CSS におけるリソースの再利用を考えた場合、もう少し別のアプローチもあります。上記のように、リソースを最利用する場合にはまずそのリソースに名前をつけて後で呼び出すというアプローチを取るわけですが、実はよく考えると CSS のルールセットというのは宣言ブロックというコード片に対してセレクタという名前をつけているといえるわけで、このネイティブな構造を利用した再利用のアプローチというのも考えることができます。実は一つの宣言ブロックに複数のセレクタをつける、というのがそれです。

//declaration block with multiple selector
#main p strong,
.alert,
.error {
  font-weight: bold;
  color: red;
}

このアプローチは非常に強力かつ、いかにも CSS らしいやり方です。というか CSS にはこの方法くらいしか効率を上げる書き方がないのですが。そしてそれ故に魅力的でどんどんこのやり方で効率的に書きたくなるものですが、実際にはすぐにセレクタの断片化という壁にぶち当たってしまいます。

CSS には大きく分けて「ビジュアルスタイルの定義 = 宣言ブロック」と「宣言ブロックをアサインする要素を指定するクエリー = セレクタ」という2種類の情報で構成されています。これらを別々に管理して書けると非常に効率的で見通しがよく、メンテナンス性も高くなって嬉しいのですがあいにく貧弱な CSS の仕様ではそういうことはできません。しかし…

extend なら宣言ブロックとセレクタを別々に管理できる

キタコレ。extend 自体の詳しい説明は他に譲りますが、つまり extend だと見た目ベースのクラス名でスタイルを作りまくって、実際に文書に適用する時にはその文書に応じたセレクタを書いて見た目ベースのスタイルを適用するということができるわけです。mixin で使った例をベースにするとこういう感じ。

//define base class
.boldred {
  font-weight: bold;
  color: red;
}

//extend base class
.error {
  @extend .boldred;
}

//output code
.error, .boldred {
  font-weight: bold;
  color: red;
}

この例だとシンプルすぎていまいち伝わらない気もするけど、この extend こそが Sass のキラーフィーチャーであり 僕が LESS より Sass 派である理由です。個人的にはこの機能はずっと以前から考えていた「ぼくがかんがえたさいきょうの CSS しよう」に入っていたのですが、初めて extend を知ったときはまさにそれがそのまま実現されていたので「あなたが神か」と思いました。

で、これだけで全て丸く収まってバンザイかというと、実はそうでもありません。再利用性という観点から extend を駆使してオレオレライブラリを作ったりするようになると新たな不都合が見えてきます。

使わないクラスで肥大するライブラリ

リソースの再利用を考えた時に次に進むステップは、再利用可能なクラスライブラリを作って、なにかプロジェクトを始めるときはそれを読み込んで使う、というものです。この考え方自体は順当で何も問題ないのですが、Sass で extend を前提にこれをやろうとすると、実際には使わないスタイル定義が大量に出力されてしまうことになります。これは extend が実際に書かれているスタイルのセレクタを拡張する、という実装なのである意味しょうがないのですが、やっぱり使ってないスタイルのコードまで出力されるのは無駄にサイズが増えるので困ります。で、これをどう解消するか。

クラス定義を出力するだけの mixin を作る

上の話に共感する人なら当然これも考えたことがあると思いますが、まあ単純な話で、まず .foo というクラスを出力するケースを考えます。

.foo {
  /* style definition */
}

そしてこのクラスを出力するだけの mixin を use_foo という名前で作ります。

@mixin use_foo {
  .foo {
    /* style definition */
  }
}

こんな風に mixin の中に閉じ込めて、実際に必要になった時だけ

@include use_foo;
p .baa {
  @extend .foo;
}

のように呼び出す、というものです。これで使わない無駄なコードが出力されることはなくなりました。めでたしめでたし。かとおもいきや、同じクラスを複数箇所で extend してる時に毎回同じ事をしてしまうと、今度は同じクラスのコードが何度も出力されてしまうことになります。include をファイル冒頭にまとめてしまって手動で管理という方法もありますが、これは煩雑で難しく、というかそもそもめんどくさいですし、さらに extend しようとするクラスが他のクラスを extend していた、というような多重継承を管理できないという問題が発生します。これを解決するには、あるクラス定義を書きだす mixin が必ず一度しか実行されない、というような singleton 的な機構があればいいことになります。やっと singleton の話出てきましたよ。タイトルだけ見るとものすごい眉唾感のある「Sass で singleton」というお題、ここまででやっと意味と需要がお分かりいただけたでしょうか。

Sass で singleton を実現する

mixin の実行を一回だけに制限するような機構は Sass にはネイティブな形ではありません。しかし Sass はそれなりにプログラマブルな機構を備えているので、これらを応用すれば何とか出来るのではないでしょうか? そこでやってみたらできた、というのが以下のやり方です。

$use_foo: false !default;
@mixin use_foo() {
  @if $use_foo {}
  @else {
    $use_foo: true;
    .foo {
      /* style definition here */
    }
  }
}

まず、先ほど作った mixin、use_foo に対して、$use_foo というグローバルなブーリアン変数を用意しています。初期値は未実行なので false です。これが、その mixin が実行済みかどうかを保持するフラグになります。次に mixin の中でこのフラグのステータスを調べます。true であれば実行済みなので何もしません。false であれば、まずこのフラグを true (=実行済み)に書き換えた上で、単にクラス定義の中身を出力します。使うときは先程と同じく単に

@include use_foo;

p .baa {
  @extend .foo;
}

とすればいいだけです。これでクラス .foo を extend したい時は @include use_foo; すればいつでもコードの重複を気にせず安全に気軽に使うことができます。

ちなみに、最初のフラグ変数に false を代入する時に !default が付いている理由ですが、これはライブラリファイル自体が依存性を持っていた場合を考慮した対処です。ライブラリファイル自体が他のライブラリファイルを import しているような場合、ベースになっているファイルAが、ファイルBからもファイルCからも読み込まれる可能性があります。この場合、単に $use_hoge: false; と指定してしまうと、既存のステータスにかかわらず未使用フラグが立ってしまい、出力の重複が発生する可能性があります。!default をつけておけば、未定義の時のみ代入が行われるので、せっかく立っているフラグをへし折らずにすみます。

以上、Sass で singleton して安心なライブラリを構築する tips でした。これで再利用可能なライブラリを作りまくってハッピーなコーディングライフをゲットしましょう! ただしあんまり再利用性にこだわると泥沼にハマるので用法・容量を守ってほどほどにね! Enjoy!