モジュール開発でのCSS設計（for Drupal8）

CSSのコーディングにおいて、プロジェクト内のチームメンバー同士でコーディングスタイルを決めていないと、それぞれが独自ルールでコーディングをしてしまったり、似たようなコードが散乱することにもなります。

それはテーマ制作の現場だけでなく、UI設計がともなうモジュール開発行うときでも同じです。

これらを解消するための指針として、[Drupal CSS コーディング スタンダード](https://www.drupal.org/docs/develop/standards/css/css-coding-standards)で規定されたガイドラインがあります。


#モジュールのCSS設計にSMACSSの考え方を導入する

Drupal CSS コーディング スタンダードでは、Drupal8からSMACSSが推奨されています。
これはテーマ制作だけではなくモジュール開発でも同じです。

SMACSSとは、Scalable and Modular Architecture for CSSの略語で「スマックス」と読みます。
SMACSSはCSSの設計手法のひとつで、CSSのルールを概念的に5つにカテゴライズした上で、それぞれの考え方や記述ルールが取り決められているのが特徴的です。


##モジュール開発にSMACSSを導入するメリット
* どのモジュールでも一貫したCSS設計になる
* メンテナンス性が向上する
* 公開画面用テーマで継承しやすいCSSコードになる


##5つのカテゴリ

SMACSSの5つのカテゴライズです。

* **base** : CSSリセット／CSSノーマライズ／HTML要素そのもののスタイリング

* **layout** : グリッドシステムを含むページの骨組み部分

* **component** : 再利用可能なUI要素

* **state** : レイアウトやモジュールの特定の状態を示すコンポーネント

* **theme** : コンポーネントに対する視覚的なデザイン適用

モジュール上のcssファイルでは、layout、component、state の3つのカテゴリに関する部分をスタイリングしていきます。

必要に応じて最低限のthemeをスタイリングをすることがありますが、baseはスタイリングしません。 詳しくは後ほど説明します。


#モジュールの場合のCSSファイル構成

Drupal CSS コーディング・スタンダードでは、モジュールに設置するCSSファイルの命名規則を以下の通りに定められています。

* **module_name.module.css** : モジュールの機能を動作させるための最小限のスタイルを定義します。SMACSSのレイアウト、コンポーネント、ステートに相当します。

* **module_name.admin.css** : モジュール管理画面に最小限のスタイルを定義します。
SMACSSのレイアウト、コンポーネント、ステートが含まれますが、テーマは含みません。

* **module_name.admin.theme.css** : モジュール管理画面の動作に視覚的なデザインを適用します。SMACSSのテーマに相当します。

* **module_name.theme.css** : モジュールの動作に視覚的なデザインを適用します。SMACSSのテーマに相当します。


##モジュール上でのベーススタイル定義

モジュール上ではSMACSSでいうところのベースを定義しません。代わりに、Drupalコアが用意している normalize.css を使用します。

フック上で core/normalize ライブラリをロードすれば、normalize.css が利用できるようになります。


**例）hook_preprocess_page()上で normalize.css をロードする**

```php
/**
* Implements hook_preprocess_HOOK() for page templates.
*
*/
function example_module_preprocess_page(&$variables) {
  $variables['#attached']['library'][] = 'core/normalize';
}
```


**ブラウザでの出力結果**

core/normalize ライブラリをロードすると、ブラウザには以下のcssファイルが出力されます。

```html
@import url("/core/assets/vendor/normalize-css/normalize.css?os6s90");
```


##Twigテンプレートファイル上でCSSファイルをロードする場合の命名規則

CSSファイルをTwigテンプレートファイル上でロードする場合、CSSファイル名とTwigテンプレートファイル名を一致させます。

たとえば、カスタムフォームのUI定義をした場合はこのようになります。

**例）twigテンプレートファイル**

```
example_module/templete/module_name-plugin-ui-form.html.twig
```

**例）twigテンプレートファイルと対になるcssファイル**

```
example_module/css/module_name-plugin-ui-form.css
```


#実際のファイル構成

<img width="688" alt="Screen Shot 2017-06-27 at 13.17.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/39468/d624edb2-b9f3-5c15-b0e4-f85ce79a3d14.png">

コントリビュートモジュールの場合は、利用者によって直接編集されることはないのでS、基本的にCSSファイルは含めずにコンパイルして出力されたCSSファイルのみが含まれています。

しかし、プロジェクトのために開発したカスタムモジュールは、開発メンバー同士でSCSSファイルを編集すると思うので、ディレクトリの切り方やコンポーネントファイルの分け方など共有しておきましょう。


#CSSファイルをロードする

##あらかじめライブラリを定義する

管理画面用、公開画面用CSSをロードするには、あらかじめ *.libraries.yml にて定義します。

<font color="red">※Drupal7まではCSSを追加するときはdrupal_add_css()、drupal_add_library() 、hook_library_info()などを利用していましたが、それらはDrupal8で廃止されたので、基本的に *.libraries.yml を利用します。</font>


**例）モジュールのコアライブラリ  *.libraries.yml**

```yaml
core:
  version: 8.x-1.0
  css:
    component:
      css/example_module.module.css: {}
  dependencies:
    - core/jquery
    - core/jquery.once
    - core/drupal
```


**例）モジュール管理画面用ライブラリ  *.libraries.yml**

```yaml
admin:
  version: 8.x-1.0
  js:
    js/example_module.admin.min.js: { minified: true }
  css:
    theme:
      css/example_module.admin.css: {}
  dependencies:
    - example_module/core
```

<font color="red">※dependenciesに指定されたライブラリは先に自動的にロードされるので依存関係を意識する必要はありません。</font>


**例）モジュール公開画面用ライブラリ  *.libraries.yml**

```yaml
theme:
  version: 8.x-1.0
  js:
    js/example_module.theme.min.js: { minified: true }
  css:
    theme:
      css/example_module.theme.css: {}
  dependencies:
    - example_module/core
```
CSSカテゴリで指定した component:、theme: の箇所は、SMACSSに準拠した5つのウェイト（base, layout, component, state, theme）を指定可能です。

ちなみに libraries.yml と同等にCSSファイルを定義可能な hook_library_info_build() というフックがあり、そちらを使ってもライブラリ定義が可能です。


#ライブラリをロードする

##1. 一般的なライブラリをロードする方法

定義したライブラリ（CSSファイル）をロードするには、通常は、hook_page_attachments()、hook_preprocess_page() などのフックを利用するのが一般的です。

```php
function example_module_page_attachments(array &$attachments) {
  $is_admin = \Drupal::service('router.admin_context')->isAdminRoute(\Symfony\Component\Routing\Route $route);
  if ($is_admin) {
    $attachments['#attached']['library'][] = 'example_module/admin';
  } else {
    $attachments['#attached']['library'][] = 'example_module/theme';
  }
}
```

```php
function example_module_preprocess_page(&$variables) {
  $variables['#attached']['library'][] =  'example_module/theme';
}
```


##2. Twigテンプレートファイル上でライブラリをロードする方法

 twig関数 attach_library() を使えば、twigテンプレートファイル上でライブラリをロードすることができます。

```html
{{ attach_library('example_module/theme') }}
<div class="message">Some markup {{ message }}</div>
```


#公開画面用テーマと相性の良いCSS設計をする

公開画面用テーマを制作する上で、プロダクトのために設計されたデザイン案の通りに厳密にスタイリングする必要があります。

そのため、モジュールが出力するCSSをそのまま使うわけにいかず、スタイルを継承してデザインを再調整することがあります。

さまざまなデザインに組み込みがしやすいよう、以下の点に気をつけましょう。

1. セレクタの階層は浅くする
2. セレクタの詳細度が低いものを使う
3. importantを使わない（ステートを除く）
4. 過度な装飾をしない
5. スタイル適用が必要なHTML要素にはクラスを付ける


##1. セレクタの階層は浅くする

セレクタの階層が浅い方がスタイル継承がしやすくなります。

```css
/* OK （セレクタ階層が浅い） */
.nav-description:first-child {...}

/* NG （セレクタ階層が深い） */
nav > ul > li > a article p.nav-description:first-child {...}
```


##2. セレクタの詳細度が低いものを使う

詳細度を低い順に並べると、全称セレクタ（*） → 要素名 → クラス → id です。
（もちろん !important を付けると最優先になります）
<br>

idセレクタは詳細度が高くなるので避けましょう。


##3. !importantを使わない（ステートを除く）

SMACSSではとくにベース、レイアウト、モジュールで !important を使うことは禁止されています。
テーマのレベルでは禁止されてはいませんが、公開画面用テーマでスタイル継承ができるようにモジュール上では常に !important は避けるべきです。
<br>

ただし、SMACSSのステートに相当する箇所では、複雑なルールセットのスタイルを上書きする必要があるため!importantの使用が例外的に許可されています。


##4. 過度な装飾をしない

モジュール上で過度な装飾をしてしまうと、公開画面用のテーマ側でその分だけスタイルの打ち消しをする作業が増えてしまいます。
モジュールはあくまで機能を提供することが目的のため、視覚的なデザインは最小限にしましょう。


##5. スタイル適用が必要なHTML要素にはクラスを付ける

モジュールが出力するHTMLコード上で、スタイル適用が必要なHTML要素にはクラスを付けます。

基本的に、CSSによるスタイル適用が必要なHTML要素にはクラスを付け、javascriptがハンドリングするためのHTML要素にはidを付ける、ということを意識しましょう。

**例）クラスとidの使い分け**

```html
<form class="example-module-searchbox">
    <div class="example-module-searchbox__description">
        <p>example_module search form</p>
    </div>
    <div class="message message--error" id="message-error">
        There is an error!
    </div>
    <div class="example-module-searchbox__form is-visible" id="example-module-searchbox__form">
      <label for="searchbox">Search</label>
      <input type="search" class="searchbox-field">
    </div>
</form>
```

以上です。

なお、SMACSSの詳しい説明は、別途こちらのQiita記事にまとめています。
[SMACSS+BEMによるテーマ設計（for Drupal8）](http://qiita.com/J_Sugar__/items/9adee163028c9910fbc6)