Vueコンポーネントに合わせたCSSの命名規則を考えた

これまでSFC(Single File Component)ではBEMによる命名を使っていましたが、コンポーネントを書くうえでは冗長だと感じました。そこで、ECSSの考え方をベースとして、SFCに合わせてカスタマイズする形に落ち着きました。

SFC向けCSSということで**SFCSS**と呼ぶことにしています。

## scopedでも命名規則は必要
まず前提として、`<style scoped>`でコンポーネントのスタイルが外に漏れないとしても、命名規則は必要になります。`scoped`だからといって適当に書いたクラス名のツケは、きっとあとあと払うことになるでしょう。

[公式のスタイルガイド](https://jp.vuejs.org/v2/style-guide/index.html#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%81%AE%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97-%E5%BF%85%E9%A0%88)に書かれているように、サードパーティのCSSが適用されないようにするためにも、コンポーネント固有のプレフィックスなどをつける必要があります。

ただしSFCでの開発は、通常のwebサイトとは性質がことなりますので、必ずしもプレフィックスが正解ではないかと思います。


## ECSSでの命名の一例
※ ECSSの考えかたなどはここでは省略します。ECSSについてはこちらのまとめが参考になります。[[ECSSの概要と考え方のまとめ
](https://qiita.com/manabuyasuda/items/37523e9d0bfbad34211f)]

一例になりますが、ECSSでは次のようなパターンで命名を行います。

```css
.namespace-Component_ChildNode-variant {}
```

`namespace`はコンポーネントが属する場所などをプリフィックスとして付与します。`-variant`はBEMでいうところのModifierです。`Component`がBlockで`ChildNode`がElementといったところでしょうか。

この命名規則をSFCに合わせて次のようにしました。

## SFCSS
SFCSSでは次のようなパターンで命名を行います。

```css
.ComponentName {}
.ComponentName_ChildNode {}
._variant {}
```

使うのはこの３つのパターンだけです。

## ComponentName
`ComponentName`はvueコンポーネントの`name`と同じ名前を使います。

```html
<template>
  <div class="MyComponent"></div>
</template>

<script>
  export default {
    name: 'MyComponent',
  }
</script>

<style lang="scss" scoped>
  .MyComponent {}
</style>
```

ルートとなるクラス名とvueコンポーネントの`name`を一致させることで、DOMノードからコンポーネントの特定が容易になります。

vue-devtoolsを使える場合にはこのような考えは不要になりますが、プロダクション環境や、vue-devtoolsがないブラウザでの検証では役に立つでしょう。

## ChildNode
`ChildNode`はコンポーネントのルート要素以外の、クラスを付与したい要素です。

例えば次のような粒度で`ChildNode`を設定します。

```html
<template>
  <div class="MyComponent">
    <h1 class="MyComponent_Heading">
      heading
    </h1>
    <ul class="MyComponent_List">
      <li class="MyComponent_ListItem"
        v-for="item in list"
      >
        {{item.label}}
      </li>
    </ul>
  </div>
</template>
```

原則として次のような`ChildNode`連続するようなクラス名は**許容しない**ものとします :no_good: 

```css
.MyComponent_List_Item {} /* bad */
```

## variant
`variant`は条件などによってスタイルを変えたい要素に使うクラスです。ECSSではクラス名に`ComponentName_ChildNode`といったセレクタをフルで含む形ですが、`scoped`を利用できるので、単一のクラスとして定義します。

`ComponentName`と`ChildNode`がパスカルケースであるのに対して、`variant`はキャメルケースで記述し、クラス名の先頭を`_`で始めます。

```css
._variantClassName {}
```

`variant`は`:class`で付け外しする用途が多いので、クオートが不要な文字列で構成しておくと都合がよいです。


### namespaceは不要
vueコンポーネントにおいて`namespace`は不要だと考えました。ECSSの`namespace`はコンポーネントの属しているエリアなどを示しますが、vueコンポーネントでは使われる場所を限定する必要はありません。

vueコンポーネントには一意の名前をつけることになるので、プレフィックスによる名前衝突の回避も必要ないでしょう。


## ルートクラスを１つに制限する
`.vue`の`<style>`の中では、ルートとなるクラス(`ComponentName`)は１つまでに制限します。スタイルは必ずコンポーネントと１対１になるものだけ記述します。

ルートクラスを複数書きたくなるような場合、それは別のコンポーネントに分けるサインかもしれません。

## `lang="scss"`を使い`&`による記述の省略を行う

たとえば、先ほどの`ChildNode`で例示した`template`では、次のように`style`を記述します。

```scss
<template>
  <div class="MyComponent">
    <h1 class="MyComponent_Heading">heading</h1>
    <ul class="MyComponent_List">
      <li class="MyComponent_ListItem"
        v-for="item in list"
        :class="{_selectedItem: isSelectedItem(item)}"
      >
        {{item.label}}
      </li>
    </ul>
  </div>
</template>

<style lang="scss" scoped>
  .MyComponent {
    &_Heading {}
    &_List {}
    &_ListItem {}
    ._selectedItem {}
  }
</style>
```

`template`でのクラス名の記述量は減りませんが、全てをフルで書く場合と比べると、`style`全体の見通しがよくなると考えました。この`&_`による省略は、コンポーネントを分割するときにも役に立ちます。


## おわりに
このSFCSSを実案件で試したところ、コンポーネントのクラス名に悩むことが減りました。かといって適当に命名してるわけではないという、うまくバランスの取れた状態で開発を進められているように思います。