背景: BEMのクラス名は長い。
みなさん、BEM使ってますか?
FLOCSSやECSSなど、BEMを前提とした命名規則をお使いの方も多いかと思いますので、そういったものを含めればBEMは日本で最も普及している命名規則と言えるのではないでしょうか。
しかし、クラス名、長いですよね。「はじめは長いんクラス名を見たときに抵抗感があったけど、使ってみるとなるほど命名に迷わなくて済むから使いやすい、今はもう慣れたよ」という人でも、本当にここまで長い名前を使わなきゃいけないのか? ということをふと疑問に思ってしまうこともあるのではないでしょうか。私も敬虔なBEM信者で、長らくBEMを使ってきましたし、その考え方は優れているからここまで普及しているのだと思っています。
一方最近、scoped なCSSを使える環境にいる人たちもいて、ぬくぬくコンポーネントごとにテキト〜なクラス名をつけても十分平和にやっていらっしゃいます。対照的に、そうでない環境でCSSの名前被りがち問題を解決する方法は、BEMの長〜い名前だけなのでしょうか?
BEMを使いつつ、長い名前を回避したい。
本エントリーでは、BEMの優れた考え方を残しつつ、長い名前を回避する命名規則S-BEMについて提案します。そのために、
- BEMのメリットとデメリットに対する私の認識を述べた上で、
- そのメリットを保ちつつ、冗長さを減らした命名規則を提案し、
- なぜそのような規則が有効なのかを解説します。
- また、考えうる反論への反論もFAQとして載せてあります
ので是非ご一読ください。
もちろん、銀の弾丸はありませんので本手法のデメリットもありますが、ぜひ建設的な議論をしたいです。どしどしツッコミお願いします。
そもそもBEMとは
本記事は、BEMを理解している人がBEMを脱する為の記事ですので、BEMについての詳細の説明はしません。
はじめにBEMのメリットについての私の認識を述べます。さっさと提案命名規則を知りたい方は次の項へどうぞ。
実用的な要素分解方針
Webページの構成要素を、ある程度のまとまりであるBlockと、それを構成するElementに分解する。これらのちょっと違うバージョンには、Modifierをつけて調整する。
という考え方です。これが多くのWebページにおいてページを構成する部品に分けるときに幅広く適用しやすい汎用的な考え方なので、BEMの人気の第一の理由と言えると思います。
明確な命名方針
Webページをどのように部品分けするのかということを決めたら、その部品全てにクラス名をつけます。このとき、CSSには「名前被りがち問題」があるのでBEM(の中でも最もよく使われているMindBEMding記法)では、次のような命名方針を取ります。
- Block には、その名前をそのままクラス名とする。ex)
.article-header
- Element には、属するBlockの名前とElementの名前を
__
で繋げる。
ex).article-header__title
- Modifierには、BlockやElementのクラス名とModifierの名前を
--
で繋げる。 ex).article-header__title--old
これによって、すべてのクラスがBlockという名の"苗字"を常に冠しているので、名前付けについて迷うことが少なくなります。また、Block名が被らない限り配下の者たちの名前がかぶる心配があり得ないということになりますので、誰かがクラス名をつけていやしないかと怯える必要が軽減されます。MindBEMding記法を用いることで、メンバーの中でクラス設計の巧拙があろうとも、初心者メンバーにこれさえ教えこめば名前空間を汚される心配がなく、共存することもできます。そのため、大規模なプロジェクトにはとくに重宝されます。
さらに、HTMLを見たときにそのクラスがどのような意図でつけられているのかがすぐにわかるのは大きなメリットです。例えば、company-feats__btn
というクラス名を見ただけで、__xxx
で終わってるので、「このクラスはcompany-featsというBlock、に属するElementであるbtnなのだな」と理解できます。また、company-feats__btn--small
というクラス名を見ただけで、--xxx
で終わってるので「このクラスはさっきのElementを通常より小さくするModifierなのだな」と理解できます。
Sass(scss)使用時のBEMのさらなるメリット
BEMは普通にCSSで書くだけでも上記のようなメリットをもたらしてくれるのですが、Sassを使うとより簡単に書けたりミスを減らすことができます。
Blockひとまとまりがscss文法においてもブロックとなる
scssのネストを使うことで、要素のまとまりを{}によって表現することができます。
.block{
&__element{
}
&__element2{
}
}
より、ひとまとまりのモジュール感が出ますね。
ファイル分けによる擬似名前空間
通常のCSSでは別の場所に同じ名前が存在していても、それらのスタイルがマージされるだけでエラーなどは出ません。これによって意図せずに既存の名前を上書きしてしまうということがあり得てしまい、CSSがすぐ破綻してしまう大きな原因です。これを私は「名前被りがち問題」と呼んでいます。
BEMはBlockの名前さえ被らなければ、名前被りがち問題を回避できると書きましたが、それには「Blockの名前さえ被らなければ」という大前提に対する保証が必要です。Sassがその最後のピースを埋めてくれます。
Sassは、最終的にひとつのcssにコンパイルすれば、いくらでもファイルを分割してわかりやすく管理することが可能です。
そこで、多くの場合BEMのBlockごとにファイルを分けるのが一般的です。これが、地味に大きいです。
つまり、BlockごとにBlock名のついたファイルに分けてしまえば、**ファイルシステムが同じファイル名を排除してくれる(!)**ので、同じ名前のBlockが存在し得ないという状況がおこってくれます。実は多くの人がBEMのここにメリットを感じているのではないでしょうか?
scss
┗ blocks
┣ _article-block.scss
┣ _company-feats.scss
┗ _profile-slide.scsss
┗ (NEW!)_article-block.scss →エラー。OSが怒ってくれる。
ここまでを使うことにより、BEM以前はどうしても安心できなかった「名前被りがち問題」がほぼ解消されたのです。
BEMのメリットまとめ
というわけで、BEMのメリットは以下の通りと考えています。
- 実用的な要素分解方針 ・・・いろいろな構造で使える
- 明確な命名方針 ・・・名前付けに迷わない
- Blockがscssでもブロック{}となる ・・・コードの見た目がわかりやすい
- ファイル分けによる擬似名前空間 ・・・ファイルシステムが名前被りを防止
BEMのデメリット
一方、BEMには以下のようなデメリットがあります。
やはり、長い・・・。
敬虔なBEM信者(私もそうでした)であろうと、心の奥底では「本当にここまで厳しい戒律を守らなければならないのだろうか?」と考えているのではないでしょうか。
例えば、マルチクラスBEMを採用しModifierを複数つけられるようにした場合、以下のような事情になります。
Blockの名前
多くの記事に出てくるようなシンプルな名前(button
など)で済む場合は少ないです。やはり他の部品と被らせない様にしようとすると2単語ぐらいにはなってしまいますよね。例えば、会社の強み/特徴をあらわすパーツに company-feats
とか、自己紹介ページにあるスライドに profile-slides
とかです。ここまでは大したことないように思えます。
Elementの名前
その下のElementには、さらにつなげて __<Element名>
がつきます。例えば、 company-feats__btn
とか、 profile-slides__slide
とかですね。現時点でかなり長いです。
Modifierの名前
Modifier にはBlock名やElement名にさらに --<Modifier名>
をつけます。company-feats__btn--small
とか profile-slides__slide--active
とかです。長いですね。。
さらに、マルチクラスBEMを使うと、一つのクラスにこうなります。
<a class="company-feats__btn company-feats__btn--small company-feats__btn--blue">詳しく見る</a>
たかが、青くて小さいボタンに。。これは、他のスコープのある言語ではしなくていい苦労 です。
aside: シングルクラスBEMかマルチクラスBEMか。
BEMのModifierの使い方には、暗黙に2つの流派が存在します。シングルクラスとマルチクラスです。シングルクラスは、
class="profile-slides__slide--active"
と指定すれば、このModifierクラスの中にElementのデフォルトのスタイルを含んだ上で、active時の追加のプロパティ(通常より濃く表示されるとか?)がついているというものです。Sass側で書くときはmixinやextendsを使うんですかね。
一方、マルチクラスでは、Modifierには修正部分のプロパティだけを書くようにします。なぜならば、複数のモディファイアを組み合わせてつけることができるようにするためです。例えば色を変えるModifier (red, blue, green)と大きさを変えるModifier (small, medium, big)を用意しておき、後から組み合わせられるようにするためです。赤くて小さなボタンとか、青くて大きいボタンを簡単に作れます。これに比較してシングルクラスだと、 article-block__btn--small--red とかいう組み合わせをいちいちそれぞれ作らなきゃいけなくなります。
私はマルチクラス派ですし。これから提案するS-BEMは非常にマルチクラスがやりやすいです。
提案命名規則 S-BEM
さて、大変前置きが長くなってしまいましたが、提案するS-BEMについてご説明します。
S-BEMでの要素分解の考え方は、BEMとほとんど同じですが、クラス名の付け方が違っており、わざわざBlock名という"苗字"をつけません。その代わり以下の3つの規則を守らなければなりません。
【規則1:b-
e-
m-
】クラス名に、役割をあらわす接頭辞をつける。
既存BEMでは、 __ がついたらelement, -- がついたら modifier と判別します。
S-BEMではより直接的に、 Blockにはb- Elementにはe- Modifierにはm- とつけます。
<div class="b-block m-modifier">
<div class="e-element">
</div>
<div class="e-youso m-shusei">
<div class="e-mago">
</div>
</div>
</div>
【規則2:>.e-
】Elementは、Blockの子要素セレクタ「 > 」で指定
e-から始まるElementクラスは必ずなんらかの b-から始まるBlockクラスの中の子要素セレクタ(もしくは子孫要素セレクタ)で指定。Elementがさらに下にElementを持つのであればさらに繋げる。
.b-block{
xxx:yyy;
...
>.e-element{
xxx:yyy;
...
}
>.e-youso{
xxx:yyy;
...
>.e-mago{
xxx:yyy;
}
}
}
【規則3:&.m-
】Modifierは、つけたいBlockやElementとマルチクラス指定する。
scssの文法を使うことで&.m-
とかなりスッキリ書くことができます。modifierは例外なくこの書き方をすればOKです。
.b-block{
...
&.m-modifier{
xxx:yyy;
}
>.e-element{
...
}
>.e-youso{
...
&.m-shusei{
xxx:yyy;
}
>.e-mago{
...
}
}
}
これがcssに変換されるとこうなります。
.b-block {
xxx: yyy;
}
.b-block.m-modifier {
/* b-blockとm-modifierが同時指定されているときのみ有効*/
xxx: yyy;
}
.b-block > .e-element {
xxx: yyy;
}
.b-block > .e-youso {
xxx: yyy;
}
.b-block > .e-youso.m-shusei {
/* e-yousoとm-shuseiが同時指定されているときのみ有効*/
xxx: yyy;
}
.b-block > .e-youso > .e-mago {
xxx: yyy;
}
HTMLでは次のように使われることになります。m-modifierは、b-blockについているときにだけ効力を発揮することになり、他のクラスについているときには何も起こりません。m-shuseiについても同様です。
<div class="b-block m-modifier"> <!-- m-modifier が有効😀 -->
<div class="e-element m-modifier"> <!-- m-modifierは何の意味もない💀 -->
</div>
<div class="e-youso m-shusei"> <!-- m-shusei が有効😀 -->
<div class="e-mago">
</div>
</div>
</div>
以上が、S-BEMのルールです。BEMの考え方を用いつつ、長い名前をつけていないことはわかっていただけたかと思います。
しかし、今までBEMの長い名前に慣れ親しんで来た人であればあるほど、「これで本当に大丈夫なの?」「BEMのメリットは損なわれていないの?」と思われるかと思います。次の節ではこのような設計になっている理由と、得られるメリットとデメリットを説明します。
解説
S-BEMの着想は、BEMのメリットを活かしつつ、名前の長さを回避したいということでした。そのため、BEMのメリットが活きているのかを見ていきましょう。
実用的な要素分解方針
要素分解方針はオリジナルのBEMから何も変えていないので、この性質は変わらず維持されます。
明確な命名方針
S-BEMでも、 命名方針はきわめて明確で、名前付けに迷うことはない でしょう。本家MindBEMdingのように __xxxで終わればElement、---yyyで終わればModifierのような暗黙の了解ではなく、b-
e-
m-
と直接文字がついているのでわかりやすいですよね。
ただ、MindBEMdingはクラス名を見ただけでどのBlockに属するElementなのかがわかるというメリットがあります。一方、S-BEMではそのElementを見ただけではそのBlockに属しているのかわかりません。この点は劣化していると言わざるを得ません。
しかし、親をたどっていって一番近い親が属するBlockとなるため、周りを見渡せばe-がどのBlockに属するのかはすぐにわかります。(この方針は、Blockの粒度を小さく保とうとするモチベーションにもなる。)
また、ブラウザの検証ツールを使えば、Styleタブに適用されているセレクタ.b-block > .e-element
が表示されるので、そのBlockに属することがすぐにわかります。
ブラウザの検証ツールを使わずにコーディングする人なんていないよね・・・?
Blockがscssでもブロック{}となる ・・・コードの見た目がわかりやすい
scssとの合わせ技で、依然としてコードの見た目はわかりやすいです。
さらに、本家BEMと違って、elementは親の元にフラットではなく、HTML構造と同じ階層構造を持っている。 つまり、SCSSからHTMLを想像して再現できる ようになっています。
ファイル分けによる擬似名前空間
この性質はもちろん変わりません。
というわけで、「e-title
と見た瞬間どのBlockに属するe-title
なのか、一瞬探さなければならないという点」のみが違う点だと思います。
また、次のような性質があります。
ElementのBlockを跨いだ名前被り、全然OK
b-some-block
の中に e-title
というElementがあったとして、別の b-another-block
の中にも同じ名前の e-title
があるかもしれません。これは許容されます。 Blockの庇護下にいるElementは自由に名前を付けてOKです。Blockの名前さえユニークであれば、その中のElementたちは安心してフツーな名前をつけてください。 田中さんの家の中にいる限りは、太郎とか一郎とか、他の家の子供と絶対被りそうな名前をつけても、間違えることはないのと似ていますね。
.b-some-block{
>.e-title{
background:red;
}
}
.b-another-block{
>.e-title{ //このe-titleは、上のe-titleとは全くの別物。お互い干渉しない。
background:red;
}
}
これは、オブジェクト指向における多態性のようなものとも言えるし、BlockがElementのスコープを作っているとも言えるかもしれません。
たしかに、本家bemは絶対的な安心感を与えてくれます。
しかし、クラス名の長さがすんごい(company-feats__btn--small)ですし、長い名前はミスも誘発しやすいです。
そこで、子要素セレクタを使えば、名前被りのリスクを回避できる。これがS-BEMです。
ぜひぜひ、議論をふっかけてください。
FAQ
・・・執筆中
そもそもBEMじゃないじゃん →定義による
e-title とか、かぶるの怖い →大丈夫信じて。
詳細度が上がるのやだ →詳細なものは強くてええねん
HTML構造が変わったらどうすんねん →SCSS構造も同期して変えたらええやん
長い名前をつけても、どうせ、elementやmodifierの再利用は推奨されない。
article-block__btn--small
は、 article-block__btn
の横につく。これ以外のblockにつけることは推奨されない。決まりきっているのに、こんな名前。。