CSS modulesを使ってCSSで名前空間を実現する

CSSは、勉強してないのをごまかすためにイキって強がる人の風評被害を受けやすく、実際よりも低く評価されてしまうことがよくあります。
でも現代のCSSは意外に良いものですし、依然として残る課題に対しても「名前空間がないからCSSマジつかえねぇわ〜」と知ったかぶりして書かない理由を探すのではなく、やはり生産的に解決したいものです。

そこで、この記事ではCSS modulesを使って **お手軽に** CSSに名前空間を擬似的に導入します。
また、ParcelでCSS modulesを使う時に遭遇するバグの回避方法もあわせてお見せします。

## CSSに名前空間がないことで起きる問題

CSSには名前空間がないため、たとえばこれからご紹介する例のように複数のCSSファイルを読み込んだ際に意図せぬ見た目になってしまうことがあります。

まず、以下の `yagi.css` だけを読み込んでみましょう。

```yagi.css
.yagi {
  width: 10em;
}

.big {
  width: 50em;
}

.yagi::after {
  content: '';
  display: block;
  background-image: url('https://user-images.githubusercontent.com/1481749/56465716-251ebf00-643f-11e9-8c66-8d0de8953663.jpg');
  background-size: contain;
  background-repeat: no-repeat;
  padding-top: calc(100% * 225 / 400);
  width: 100%;
}
```


```yagi.html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="yagi.css">
</head>
<body>
  <div class="yagi"></div>
  <div class="yagi big"></div>
</body>
</html>
```

![yagi0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16345/02be8def-ff28-0c38-63e0-c18ba7f7050e.png)

`yagi.css` に設定した `.big` クラスの設定が効いて $\huge{大きなヤギ}$ の画像になっています。

次に、以下の `button.css` だけを読み込んでみましょう。

```button.css
.button {
  padding: 0.6em;
  font-size: 1em;
}

.big {
  font-size: 3em;
}
```

```button.html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="button.css">
</head>
<body>
  <button type="button" class="button">Button</button>
  <button type="button" class="button big">Big Button</button>
</body>
</html>
```

![button0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16345/e116eca9-e521-0d7d-a008-68f612b7b493.png)

`button.css` に設定した `.big` クラスが効いてボタンが大きくなっています。

では、`yagi.css` と `button.css` を両方読み込むとどうなるでしょうか。

```mix.html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="yagi.css">
  <link rel="stylesheet" href="button.css">
</head>
<body>
  <button type="button" class="button">Button</button>
  <button type="button" class="button big">Big Button</button>
  <div class="yagi"></div>
  <div class="yagi big"></div>
</body>
</html>
```

![mix.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16345/c9482ced-23a8-efcf-0b30-3a9a83f9792c.png)

<font color="red">ぶっこわれました。</font>

ヤギ画像にもボタンにも以下の **両方の** CSSが適用されてしまったためです。

```yagi.css
.big {
  width: 50em;
}
```

```button.css
.big {
  font-size: 3em;
}
```

このように、一般的過ぎるクラス名を使うと、意図しない要素にそのCSSが影響してしまいます。

## BEM記法

技術ではなく運用でこの問題を解決しようとするのが[BEM記法](http://getbem.com/)です。
先ほどの例では、以下のようにクラス名をつけなおします。

```yagi-bem.css
.yagi {
  width: 10em;
}

.yagi--big {
  width: 50em;
}

.yagi::after {
  content: '';
  display: block;
  background-image: url('https://user-images.githubusercontent.com/1481749/56465716-251ebf00-643f-11e9-8c66-8d0de8953663.jpg');
  background-size: contain;
  background-repeat: no-repeat;
  padding-top: calc(100% * 225 / 400);
  width: 100%;
}
```

```button-bem.css
.button {
  padding: 0.6em;
  font-size: 1em;
}

.button--big {
  font-size: 3em;
}
```

手作業でクラス名の頭に識別子をちまちまくっつける感じですね！
悪くはないんですが、識別子を付けないやつがチームにいると困ったことになります。

また、いくらベラのキャラデザインを現代的な若い女の子にしても、同時期に放映されているゲゲゲの鬼太郎もネコ娘のキャラデザインが一新されており、脚本も攻めまくったおもしろい内容になっているので「まぁ鬼太郎があるし、妖怪人間の方は見なくていいか」となってしまいます。

## CSS modules で解決する

そこで、人間の手にたよらずに、ヤギの手も妖怪の手も借りずに自動で名前の衝突を避けてくれる技術の1つが CSS modules です。

では、お手軽にためしてみましょう。
[node](https://nodejs.org/)をインストールしていない方は先にインストールしておいてください。
以下はLinux OS上での操作を想定していますが、Macでもだいたい一緒だと思います。
うぃんどうずを使っている人はまずデュアルブートでUbuntuとかを入れましょう。

`css-modules-sample` みたいなディレクトリを作り、そのディレクトリの中で

```bash
$ npm i postcss-cli postcss-modules
```

を実行して必要なライブラリをローカルインストールしましょう。
あとは、CSSファイルと以下の設定ファイルをこのディレクトリに置いておきます。

(設定ファイルの詳しい仕様を知りたくなったら[公式ドキュメント](https://github.com/michael-ciniawsky/postcss-load-config)をご参照ください)

```.postcssrc.js
module.exports =
  {
    "modules": true,
    "plugins": {
      "postcss-modules": {},
    }
  }
```

これで準備は完了です。
このディレクトリ内に `yagi.css` を用意して、以下のコマンドを実行してみましょう。

```bash
$ npx postcss yagi.css -o dist/yagi.css
```

あらたに `yagi.css.json` と `dist/yagi.css` が生成されているはずです。
`dist/yagi.css` をひらいてみると、以下のような内容になっています。

```dist/yagi.css
._yagi_1d9sa_1 {
  width: 10em;
}

._big_1d9sa_5 {
  width: 50em;
}

._yagi_1d9sa_1::after {
  content: '';
  display: block;
  background-image: url('https://user-images.githubusercontent.com/1481749/56465716-251ebf00-643f-11e9-8c66-8d0de8953663.jpg');
  background-size: contain;
  background-repeat: no-repeat;
  padding-top: calc(100% * 225 / 400);
  width: 100%;
}
```

なんかランダムっぽい識別子がくっついて名前の衝突を防げるようになっていますね？

`yagi.css.json` は、オリジナルのクラス名がCSS modulesによってどのように変換されたかをJSON形式で保持しています。

```yagi.css.json
{"yagi":"_yagi_1d9sa_1","big":"_big_1d9sa_5"}
```

なんらかの工夫をして、HTML側のタグにつけるクラス名もこのJSONファイルを見て変換しないといけません。
Reactとかだと何かそういうライブラリとかがあるみたいです。
でも、ちょっとそれってめんどくさいですよね？
そこでそんなめんどくさがりの人のために、**ずるいやり方** があります。

設定ファイル `postcssrc.js` を以下のように書き換えてしまってください。

```postcssrc.js
module.exports =
  {
    "modules": true,
    "plugins": {
      "postcss-modules": {
        "generateScopedName": "[name]__[local]",
        "getJSON": () => null
      }
    }
  }
```

[postcss-modulesの説明](https://github.com/css-modules/postcss-modules#usage)にあるとおり、

* `generateScopedName` は「どのようにクラス名を変換するか」のルールを指定し
* `getJSON` は変換の対応表の書き出し方を指定します

ここでは `generateScopedName` に `ファイル名__元のクラス名` という形式を指定し、`getJSON` には対応表は特に生成しないように指定しています。
実行して結果を見てみましょう。

```bash
$ npx postcss yagi.css -o dist/yagi.css
```

```dist/yagi.css
.yagi__yagi {
  width: 10em;
}

.yagi__big {
  width: 50em;
}

.yagi__yagi::after {
  content: '';
  display: block;
  background-image: url('https://user-images.githubusercontent.com/1481749/56465716-251ebf00-643f-11e9-8c66-8d0de8953663.jpg');
  background-size: contain;
  background-repeat: no-repeat;
  padding-top: calc(100% * 225 / 400);
  width: 100%;
}
```

これなら事前にどのようなクラス名に変換されるか予測できるため、HTML側を書く時に

```mix2.html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="yagi.css">
  <link rel="stylesheet" href="button.css">
</head>
<body>
  <button type="button" class="button__button">Button</button>
  <button type="button" class="button__button button__big">Big Button</button>
  <div class="yagi__yagi"></div>
  <div class="yagi__yagi yagi__big"></div>
</body>
</html>
```

としておけばいいだけです。

Elmを使っている方は、[「Elmでもっと気軽にCSS modules」で紹介した方法](https://qiita.com/arowM/items/ba08d0a705eef2626b69#elm-%E5%81%B4%E3%81%AE%E5%B7%A5%E5%A4%AB)を使うと簡単です。

## Parcelで使う

ファイルを結合したり、Babelを使ったり、Minifyしてくれたり、そういう面倒なタスクをお手軽にいろいろ勝手にやってくれる技術の1つに[Parcel](https://parceljs.org/)があります。
Parcelを使う場合は先ほどの `.postcssrc.js` をディレクトリ内に置いた状態で

```bash
$ npm i parcel https://github.com/arowM/parcel/archive/parcel-bundler@1.12.4.tar.gz
$ parcel build mix2.html --public-url ./
```

とすれば、`dist` にコンパイル後のファイルが生成されます。

[サンプルリポジトリ](https://github.com/arowM/parcel-cssmodules) を作っておいたので、このリポジトリをcloneして

```bash
$ npm i && npm run build
```

としても同じことができます。

![mix2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16345/f3459628-3a06-139d-bc64-1c7d42d3397b.png)

**この通り、うまくいきました！**

なお、Parcel には [postcss-modules の `generateScopedName` が効かないという問題](https://github.com/parcel-bundler/parcel/issues/972)があり、これを修正したものがまだリリースされていません。
そこで、上記のコマンドやサンプルリポジトリでは、parcelをフォークして独自に修正した [parcel-bundler@1.12.4](https://github.com/arowM/parcel/releases/tag/parcel-bundler%401.12.4) をインストールしています。



[さくらちゃんにご飯をあげる](https://www.amazon.co.jp/gp/registry/wishlist/20ZV2VC57615B/ref=nav_wishlist_lists_1)
[さくらちゃんをもっと見る](https://twitter.com/hashtag/%E3%81%95%E3%81%8F%E3%82%89%E3%81%A1%E3%82%83%E3%82%93%E6%97%A5%E8%A8%98?src=hash)
[他の記事を見る](https://qiita.com/arowM)
![sakura.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16345/1fea1aa2-0e14-168e-8a4a-96f37cb1df10.jpeg)