Reactを使ったWebアプリケーションを開発する際、スタイル定義にCSS Modulesを使うことで、いろいろなメリットを得ることができます。 この記事では、CSS Modulesの概要とreact-css-modulesによる導入方法についてまとめました。

CSS設計の問題点

これはReactコンポーネントに限らない話ですが、CSSを設計する際、一般的に次のような問題があります。

  1. スコープがグローバルになる
  2. クラス定義の場所が一意的でない
  3. ファイル分割の指針が開発者に委ねられる

これを解決する既存の方法として、1はBEMなどの設計手法を取り入れたり、2-3はチーム内でルールを整備する、などが考えられます。

ただ、Webアプリケーションのコンポーネントは原則的に閉じた世界なので、閉じられたコンポーネント単位でスタイルを用意し、これに一意のローカルなクラス名を与えれば、上記の問題はすべて解決することができます。

これを実現するのがCSS Modulesです。

CSS Modulesとは

CSS Modulesは、すべてのクラス名が標準でローカルスコープとなるCSSファイルのこと、またそれを扱う仕組みのことをいいます。

1つのコンポーネントに対応するCSSを用意すれば、そのCSSは対応するコンポーネント専用になり、たとえクラス名が被っても他のコンポーネントに影響を与えることはありません。 BEMのような長ったらしいクラス名は必要なくなります。

また、CSSはコンポーネントから参照するため、クラス定義の場所も分かりやすく、必然的にファイル分割の指針もできます。

css-loaderによるモジュール化

webpackのcss-loaderを使うと、次のように書けます。

デフォルトではグローバルスコープになるため、ローカルスコープにするには:local構文をつける必要があります。

.globalClass {
  ...
}

:local(.localClass) {
  ...
}

こうすることで、次のようなrender()メソッドをもつReactコンポーネントを書くと:

render() {
  return (
    <span className={styles.localClass + ' globalClass'}></span>
  );
}

次のようなHTMLが出力されます。

<span class="EYtsuvGX0ck2MVhh_I1Zc globalClass"></span>

このように、.localClassが一意のクラス名になり、他のスタイルとの衝突の可能性がなくなります。

問題点

css-loaderによる対応では、次のような問題が残ります。

  • classNameにグローバルスコープとローカルスコープのクラス名が混在しており分かりづらい
  • クラス名にキャメルケースしかつかえない
  • stylesオブジェクトを毎回参照しなければならない

BootstrapなどのCSSフレームワークを導入する場合、グローバルスコープをもつクラスを頻繁に付与することになり、このままでは可読性が大きく下がってしまいます。

この問題を解決する方法として、Reactの場合react-css-modulesを用いる方法があります。 これにより、HTMLを次のように書けます。

<div className='global-class' styleName='local-class'></div>

この導入について以下で説明します。

動作環境

この記事の内容は、次の各バージョンで動作を確認しています。

  • webpack v1.13.2
  • css-loader v0.25.0
  • react-css-modules v3.7.10

手順

1. 開発環境を構築する

まず、webpackによる開発環境を構築しておく必要があります。 まだの場合は、次の記事を参考に構築します。

2. インストールする

次に、react-css-modulesをインストールします。

$ npm install --save react-css-modules

3. ローダーの設定を変更する

上記の記事の設定の中で、ローダーの設定として次のようにmoduleslocalIdentNameの2つのパラメータを付与します。 この設定では、次の2点を行なっています。

  1. クラス定義のデフォルトスコープをグローバルからローカルに変更する
  2. 生成されるクラス名を分かりやすくする(デバッグ対策)
module.exports = {
  module: {
    loaders: [
      {
        test: /\.s?css$/,
        loader: ExtractTextPlugin.extract([
          'css?modules&localIdentName=[name]---[local]---[hash:base64:8]',
          'sass'
        ])
      }
    ]
  }
}

4. 実装する

以上の設定により、次のようにスタイル定義を行なっていけるようになります。

.local-class {
  :
}

:global(.global-class) {
  :
}
import React, { Component } from 'react';
import CSSModules from 'react-css-modules';
import styles from './path/to/css';

class App extends Component {
  render() {
    return (
      <div styleName='local-class' className='global-class'></div>
    );
  }
}

export default CSSModules(App, styles);

おまけ

上記の設定で、デフォルトスコープがローカルになりました。

ただ、CSSフレームワークをimportするときなどはグローバルで読みたいと思います。 これは、次のように書けばよいです。

:global {
  @import './path/to/css';
}

参考記事