CSS
Sass
scss
ポエム

プログラマーから見た、SCSSの正しい(かもしれない)使いかた

SCSSとは

SCSSというのは、CSSのアレなところを何とかしようという目的で作成されたメタ言語です。詳細は省略します。

なにそれ? ってかたは、コチラなどがわかりやすいのではないかと思います。
CSSのメタ言語Sass(SCSS)、LESSの完全入門

でですね。

ここで大事なのは、こいつは要するにCSS周りの技術ですので、つまるところは基本「デザイナーさんが使う」ものである、というところです。

彼らは概して、非常におおらかで、健康的で、寛容です。私たちサーバーサイドエンジニアのように、細かいことで「こんな仕様は許されんな!」「なんだよこの仕様設計したヤツって○○じゃねーの?」などとイラついたりしません。
なので、あんな欠点だらけのクソ規格であるCSSにも、特に気にしたりしないらしいのです。

ところがやはり、中には「CSSのそーゆーところって、やっぱ問題だよねー」って思う人々もいたらしく、CSSの定義を再利用できるような仕組みとして、SCSS(やSASSやLESS。以下SCSSにまとめます)が誕生したわけです。

ですが。
デザイナーの皆さんは前述の通り非常におおらかなので、そのような方々がブログなどでご説明くださっているSCSSの解説などを拝見すると、、、、
どうもあまりにおおらかすぎて、プログラマーから見て「そこはそういう解釈ではないのでは、、、」と思ってしまう部分がしばしばあります。

そんなわけでデザイナーの皆様の前で大変僭越ながら、プログラマー視点から見たSCSSの、特に@extend,@mixinまわりの解釈をまとめたいと思います。

@extend@mixinの使い分け」?

Webの世界を徘徊していると、「@extend@mixinの使い分け」というエントリーをしばしば見かけます。
私はプログラマーなので、この方面には素人です。ので、こういうエントリーを読んだ時の自分の違和感は、きっと素人でわかってないからに違いない、と当初は考えていました。
ですが、結論としては

  • @extend@mixinは、大根とニンジンくらい違う
    • つまり、形はちょっとだけ似ているけど、用途はまったく違う
  • んなわけで、使い分けとかそもそも意味不明

なんじゃないかな?
という感覚を得ております。

@extend

日本語にすると「継承」1。この概念は、プログラマの世界では、オブジェクト指向の基本中の基本と言われるものです。
例えばJavaの世界では「既存のクラスを基底として、それに似ているけどちょっと違う別のクラスを作成する」時に使用されます。
SCSSの@extendも同じ。
「既存のクラスを利用して、ほとんど同じだけどちょっと違う別のクラスを作る」ための技術です。
が。
デザイナーの皆さんの説明の中には、この「既存の」クラス、という概念がひっっじょーに希薄に見えます。
だから@mixinとの使い分けなんてわけわからないところで悩むんじゃない? と思ったり。思わなかったり。

例えば。
プロジェクト全体の共通SCSSで、以下のようなクラスが与えられていたとします。
# この「与えられていた」っていうのが重要

common.scss
.tableHead {
    background-color: #222;
    color: #eee;
    font-size: 12px;
}

表のヘッダにはこのクラスを使うように統一しましょう、と。
でも、自分が担当するページの表では、フォントサイズが12pxだとどうも大きすぎる。10pxに抑えたい。
だけど言うまでもなく、与えられたcommon.scssを勝手に書き換えたらアカン。みんなに殺される。
そう! こういう時に使うんだよ、@extendは!

mytable.scss
@import "common.scss";   // 既存のscssをインポート

.myTableHead {
    @extend .tableHead;  // 既存のクラスが元になっている
    font-size: 10px;     // ここだけ変更
}

このように、共通クラスの.tableHeadのほとんどをパクり、フォントサイズだけ独自に10pxに再定義した、.myTableHeadを簡単に作成することができました。

余談ですが、Javaの世界ではこういう風に、基底クラスの一部だけを上書きするように再定義することを「オーバーライド」といいます。
@extendも基本的な使い方は同じ。

つまり、@extendの使用用途は、この例のように

  • 既存の.tableHeadの一部を変えたい
  • でも、みんなで使ってるから、勝手に書き変えることはできない
  • だからつって、似ている別のモノを新しく作るのは非効率だし
  • 元のデザインが変わったときに、似てるもの全部手で直さないといけないのはクソめんどい。

こんな時に使うんじゃない? @extendって。
つまり

  • .tableHeadの大部分を@extendでパクって
  • 一部だけ書き換えて、新しい別のクラスを作る。

違うかな。

@mixin

@mixinは関数である。
なんて書かれていたりもします。
全然ちげーよ! じゃあ@functionはなんなんだよ!!
……あらいけませんわ、ワタクシったら。オホホホホ。。。

mixin(ミックスイン、もしくはミキシン。ふりかけとか調味料とかいう意味)という概念。私がこれを初めて知ったのはCLOS(Common Lisp Object System)というプログラミング言語に触れたときです。
このmixinという仕組みは、「それ単独で使うのではなく、他のクラスと合体させて、その一部分として使うためのクラスのパーツみたいなもの」です。
文章で書いてもわかりづらいので、実例で。

common.scss
@mixin tableHeadMixin {  // 背景色と文字色だけ指定した未完成品
    background-color: #222;
    color: #eee;
}

共通チームの人が気を利かせて、フォントサイズの融通が利くテーブルヘッダ用のmixinを作ってくれたわけですな。フォントサイズの指定がありません。
こいつをふりかければ、どのクラスも同じ背景色と文字色になる。つまり、ふりかければどれも同じ味になる、ということです。
そして、もちろんコレ単独でクラスとしては使えません。定義のアタマに@mixinって書いてありますからね。
そもそも、ふりかけは単独では食べません。

で、実際のクラスを作成するときには、まずはフォントサイズだけ定義して、あとはこのmixinを振りかければ、ふりかけ味の色指定ができてるクラスが簡単にできますよー、という使い方をします。

mytable.scss
@import "common.scss";   // 既存のscssをインポート

.myTableHead {
    font-size: 10px;     // フォントサイズさえ定義すれば
    @include tableHeadMixin;  // あとはmixinをふりかければ色指定も完成
}

というわけで、「背景色と文字色の定義」が再利用できる。あちこちにふりかけられる。
これがmixinの使い方です。

整理すると。
@extendでは、基本になるクラスは自分が@extendされるかどうかなんてわかりませんし、そもそもわかる必要がありません。別のクラスに勝手に@extendされます。
パクられてる側には、パクられてる意識なんてないのが普通です。
「既存のフツーのクラスに属性を追加したり、書き換えたりして新しい別のクラスを作る」のが@extendの機能です。

反面、@mixinでは、最初からmixin(ふりかけ)として使用される前提で作成されます。そしてそれを、他のクラスの上にふりかけて使ってもらう。
「これから作るたくさんのクラスの共通の部分を抽出して、ふりかけとしてまとめる」のが@mixinの機能です。
@extendと違って、@mixinでは、使う側・使われる側の双方に合意が必要です。

これが基本です。目的がそもそも違うのです。

精神論?

ちまたでは、「@extend@mixinで代用が可能」という記述があふれていて、どっちもおんなじようなもんだと思っている人も多いようですが、それは違います。
もちろん代用が可能なケースはあります。ですが、それは一部です。

と書くと、ここまで書かれてきたことも含めて、偏屈な原理主義のエンジニアが大好きな、精神論でべき論でお作法論で説教か、という印象を持たれるかもしれません。
ですが、この記事ではここまで、「既存のクラスを元にして新しいクラスを作るときには@extendを使う」べき、という話をしているのではないんです。

@extendの例で上げたような、共通ライブラリのような既存のクラス(つまり、それ自体の内容をいじってはいけないようなクラス)を元にして新しいクラスを作る場合には、@mixinは使えません。@mixinで代用することは、技術的に不可能です
なぜなら、@mixinを記述する時は、使われる側には@mixinという宣言が必要で、使う側では@includeという記述が必須です。
つまり、どちらもcssのクラスの体裁は取ることはできないわけです。既存のcssクラスをパーツとして使用することは@mixinではできないのです。

また同様に、cssファイルの中で記述されているクラスを元にする場合(詳細は後述)にも、@mixinは使えません。(なぜなら言うまでもなく、cssファイルの中に@mixin構文を記述したら、その時点でそれはcssではなくなってしまうから。)

つまり、これらのケースでは、@mixinでは代用できず@extendを使う以外の方法がない、ということです。

とはいえ

基本の考えかたがこうだとしても、代用できるケースもありますし、この手の技術は濫用も可能ですからなー。
それに、共通SCSSを作成する人が「テーブルヘッダのクラス、普通のクラスで作る? それともmixinで作る?」なんて尋ねてくれるいい人であれば、要するに選択の余地がある、ということですからね。
そういうシチュも考えられるとすれば、「使い分けなんて概念は不要!」とバッサリ切り捨てることもできませんよね。はい、スミマセン。

じゃあ、どっちを使うべきか迷うような時、どうしたらいいか。
プログラミングの歴史をヒントに考えれば……

まず、@extendと同じ仕組みを持つプログラミング言語っていうのは、非常にたくさんあります。というか、この仕組み(継承)がなければオブジェクト指向言語とは言えない!と言われています。超メジャーな仕組みです。
反面、@mixinと同様の仕組みを持つプログラミング言語っていうのは、実はほとんど無いです。私が知っている範囲では、CLOSとRubyくらい。
つまり、あまり評判がよろしくない。というか、mixinは使いこなすのがとても難しい、と言われているんです。
なぜなら、mixinは「保守性が悪い」と言われているから。

例を挙げましょうか?
AさんとBさんが、別々にmixinを作っていたとします。

a.scss
@mixin .mixinA {  // Aさんのmixin
    background-color: #222;
    color: #eee;
}
b.scss
@mixin .mixinB {  // Bさんのmixin
    background-color: #fff;
    padding: 2px;
    margin: 5px;
}

別々の目的で作られたmixinなので、両方を使う、という可能性はあります。
ですが、background-colorがバッティングしていますでしょ?
そういうのを解決する手段が、mixinには仕組みとして用意されてないんです。だいたいの言語では、あとでふりかけた方が上書きするので優先、というルールになっています。
意図的に上書きしている分には問題ないのですが、問題はうっかりミスでバッティングしてしまった場合。これは見つけるのが大変です。目で全部追わないといけない。
ですので、その辺を人間的なルールで解決しなければいけないんですよね。

個人的な経験では、mixinを全部自分一人で作っているのであれば、管理はそれほど難しくありません。
ですが、別々の人が作ったmixinを複数使うと、途端に問題が出てきます。

実はSCSSでは、@extendは複数使える(つまり、多重継承ができる)というルールなので、これはこれで保守性に問題があるのですが、@extendは1つまで、というルールを明確にすれば、ある程度は軽減できそうです。(例えばJavaがそうです)

ですが、mixinは仕組み上、1つまでというルールはイマイチそぐわないのですよ。複数ふりかけてこそmixinの真価は発揮されるという部分があるので。個人的には2つ~6つくらいmixinを使うと、そのありがたみを実感するという感覚があります。

もちろんプログラミング言語のextendやmixinと、SCSSのそれは、概念的には同じですし、実際に使い方も似てはいますが、実際にはやはり別物です。同じに論じていいかというと話は別です。
ただ、プログラミング言語の世界で「ダメよ」って言われていることは参考になるかと思います。

余談:@extendの裏技

@extendは前述の通り、「既存のクラスを利用して、別のクラスを作る」という仕組みです。
で、この「既存のクラス」、例えばプロジェクト共通のスタイルシートがscssファイルで提供されればいいんですが、実際にはcssで提供されちゃったりする(なんてこともアルアルなんじゃないかと思います。デザイナーじゃないのでよくわかりませんケド。。。)

というわけで、そのcssをインポートして中のクラスをextendするかー、と思うわけですが、実はそう簡単ではないんですな。
SCSSもここはイケてねーなーったくよぉ、と思うんですが、SCSSの@importには実はクセがあって、変な仕様なんですよ。
具体的には、@importするのが.scssファイルか、.cssファイルかで、コンパイル時の挙動が全然違うんです。2
例を挙げますと。
@importで別のファイルをインポートするとき、scssファイルをインポートすると

common.scss
.tableHead {
    background-color: #222;
    color: #eee;
    font-size: 12px;
}
test.scss
@import "common.scss";   // 既存のscssをインポート

.myTableHead {
    @extend .tableHead;  // 既存のクラスが元になっている
    font-size: 10px;     // ここだけ変更
}

生成されるのは

test.css
.tableHead, .myTableHead {
  background-color: #222;
  color: #eee;
  font-size: 12px;
}

.myTableHead {
  font-size: 10px;
}

とまあ期待通りの内容になります。

ところが!
ファイルの内容がまったく同じでも、拡張子をcssに変えただけで、@import時のSCSSコンパイラの挙動がまったく変わるんです。

common.css
/*これはscssではなく、cssファイルです。*/
.tableHead {
    background-color: #222;
    color: #eee;
    font-size: 12px;
}
test.scss
@import "common.css";   // cssをインポート

.myTableHead {
    @extend .tableHead;  // common.cssの中のクラスが元になっている
    font-size: 10px;     // ここだけ変更
}

結果こんなふうに。

test.css
@import url(test.css);   // 中身を展開してくれてない!!

.myTableHead {
  // @extend .tableHead; // 当然この行はコンパイルエラーになる
  font-size: 10px;
}

ってなわけで、@importするのがscssファイルなら問題なくコンパイルできても、同じ内容のcssファイルをインポートしようとすると、いきなりコンパイルできなくなります。

じゃあどうすればいいのか、って話なんですが。
SCSSはCSSの上位互換なので、common.csscommon.scssにリネームすればそのままscssファイルとして使えて解決ですし、実際にそうしているかたもいらっしゃるようなんですが、commonってくらいだから、内容を変えてはいないといえ、ファイル名を変えるのもちょっと抵抗がありますよね?

で、実際にみなさんこれでお困りのようなんですが、これはですね。
common.cssのシンボリックリンクで、拡張子が.scssのもの(例えばcommon.css.scss)を作成すれば解決です。3

common.css
/*共通のcssファイル*/
.tableHead {
    background-color: #222;
    color: #eee;
    font-size: 12px;
}
test.scss
@import "common.css.scss";   // シンボリックリンクをインポート

.myTableHead {
    @extend .tableHead;  // ここで再利用
    font-size: 10px;     
}

結果はめでたく以下のように期待通りの結果が得られます。

test.css
.tableHead, .myTableHead {
  background-color: #222;
  color: #eee;
  font-size: 12px;
}

.myTableHead {
  font-size: 10px;
}

シンボリックリンクを使えば、もともとのcommon.cssに修正が入っても、common.css.scss に自動的に反映されますからね。管理コストも必要ありませんし、ワタクシのようなうっかりさんにも安心というわけです。

僭越ながら、ついでに申し上げますと

@extendを使ったときに生成される以下のようなcssコード。

test.css
.tableHead, .myTableHead {
  background-color: #222;
  color: #eee;
  font-size: 12px;
}

.myTableHead {
  font-size: 10px;
}

.myTableHeadの内容が、上下2つに別れてしまうので「メンテナンス性が悪い」というご意見をたまに拝見いたします。
プログラマーの視点から見解を述べますと

コンパイラが生成したcssファイルをメンテしたらアカンがな!

と思ったりします。


  1. extendは日本語に「訳すると」、継承ではなく「拡張」になりますけどね。でもextendするときに「拡張して」と言う人を見たことがありません。 

  2. Version 3.x までのクソ仕様。@importはVersion 4で大幅に改善される計画があるようです。 

  3. Windowsで開発してるからシンボリックリンクなんて使えねーよ、という方。案外知られてませんが、Windowsでもシンボリックリンク使えますよ! あと、シンボリックリンクってなんだよ、そんなの知らねーよ、難しいこと言って悦に入ってんじゃねーよ、という方。シンボリックリンクはすげー便利な仕組みです。この機会に知っておくべきです! どちらの方も、Google先生に訊いてみよう!