Hail2u.net

Sassの変数命名規則とBEM

$type-subtype-component-contextというような形でSassの変数を命名していたけど、これにもBEMを使うかという感じになってきた。ただでさえ長いのが、セパレーターで更に長くなるけど、元々そんなに短くないので気にしないことにした。BEMをクラス名で使うような形で単純に利用するケースと、3.3で導入される予定のマップを使って構造化して定義し、複雑に参照するケースを比較して検証している。

検索ボックスに使う、以下の8つの色の変数定義を例にする。

BEMを使った簡単な実装

$color-bg_searchbox: #f9f9f9;

$color-fg_searchbox__input: black;
$color-bg_searchbox__input: white;
$color-border_searchbox__input: lighten(black, 25%);
$color-border_searchbox__input--focus: cyan;

$color-fg_searchbox__button: white;
$color-bg_searchbox__button: grayscale(mix(cyan, white, 60%));
$color-bg_searchbox__button--hover: cyan;

まず、変数の型をプリフィックスとして付けたい。変数を参照する時は「どういう変数を参照するか」が優先的に意識されるので、先に型があった方が良い。ハンガリアン記法と同じと考えるとわかりやすそう。IDEの補完に向くとかそういう実用上の理由もある。

後はアンダースコア(_)1つを区切りにしてBEMのブロック、アンダースコア(_)2つでエレメント、ハイフン(-)2つでモディファイアと繋げる。

マップを使って構造化

$searchbox: (
  color_bg: #f9f9f9,

  input: (
    color_fg: black,
    color_bg: white,
    color_border: lighten(black, 25%),
    _focus: (
      color_border: cyan
    )
  ),

  button: (
    color_fg: white,
    color_bg: grayscale(mix(cyan, white, 60%)),
    _hover: (
      color_bg: cyan
    )
  )
);

Sass 3.3で導入されることになっているマップを使う場合は、型で大きなマップを作ると巨大な変数になってしまうので、まずブロックをルートに作る。そのキーで変数の型を指定するわけだけど、マップをネストした場合はエレメントとする。モディファイアもマップのネストだけど、キーの先頭に_を付けて区別する。

参照はmap-get()関数を使う。

.searchbox {
  background-color: map-get($searchbox, color_bg);

  input {
    $input: map-get($searchbox, input);

      border: 1px solid map-get($input, color_border);
color: map-get($input, color_fg);
       background-color: map-get($input, color_bg);

       &:focus {
         $focus: map-get($input, _focus);

         border-color: map-get($focus, color_border);
       }
  }
}

map-get()関数を直接ネストすると読みづらいし、繰り返し書く必要が出てくる。普通にエレメントやモディファイア単位でネストを作ると思うので、そこでローカル変数を作ってやると良い。

ちゃんと実戦投入してないので、うまく動かないかもしれない……。


BEM手法の単純な利用は長い気持ち悪いに目をつぶれば、明確なルールに成りうるし、特にBEM道的なものから外れた使い方でもない。ただしSassの変数では-_が同一視される仕様があるので、エラーを特定しにくく、安心して使えるとは言い難い。コードが間違っているのにエラーなしに正常にコンパイルされてしまうので、変数のあたりで問題が起こった時に混乱する。

マップは未知数だけど、BEMのようなツリー構造を持つシステムには良くマッチしている。map-get()関数の使われ方もCSS Variablesのvar()とちょっと似ていて、それほどCSSからかけ離れた感じでもないんじゃないかと思う。


将来のSass(3.3でではない)ではローカル・コンテキストでの変数はそのスコープで新しく作成され、スコープを抜けると削除されるらしいので、ローカル・コンテキストで変数を作るようにすれば$type_subtypeくらいで済むようになる。BEMツリーの再現もファイルベースとセレクターのネストでやれば良い。けど、使えるようになるまではかなりの時間がかかりそうなので、それまで安定して使える手法として上記のいずれかか他の何かを確立したい。