社内でCSSの新しい方針について話したメモ


社内で新しいドメインを設立するにあたり、CSS Modules, PostCSS, cssnextを試してみました。
このスライドは、その際の説明に使ったものです。せっかくなので公開します。

「プロトタイプ作成で試してみたけど、みなさんどう思いますか?」くらいの温度感。本番採用が確定したわけではありません。何かお役に立つことがアレば幸いです。


以後、説明に使ったスライド。


おしながき

  • 1. コンポーネント時代のスタイリング
  • 2. グローバルCSS、BEM、そしてローカルCSS
  • 3. CSS Modules、そしてJSXへの割り振り
  • 4. cssnextと、その書き方
  • 5. 我々のPostCSSスタンダード

新ドメインの
CSS環境(案)


CSS Modules
css next
PostCSS


on webpack



何が変わるのか


  • 我々の今までのスタイリング
    • sassで書く
    • スタイルのモジュール化とネスト、そしてbodyクラスを駆使して、スタイルの衝突を防ぐ
    • gulpでconcatした1枚の巨大なCSSをHTMLから読み込む

  • これからのスタイリング
    • (ほぼ)生のCSSを書く
    • クラス名の衝突を気にする必要はない。かつ、BEM等は使わない
    • クラス名をJavaScriptにimportして、JSXにバインドする
    • webpackでconcatした1枚のCSSをHTMLから読み込む

順を追って説明します。


1. コンポーネント時代のスタイリング


ここでのコンポーネントは、CSSを単にカプセル化するという意味ではなく、Reactのコンポーネントに対応してコンポーネント化するという意味。



ボタンコンポーネント

// Button.jsx
const Button = ({ onChangeButton }) => (
  <div>
    <button
      onClick={onChangeButton}
    >
    変更
    </button>
  </div>
)

※例なのでけっこう雑です


対応するCSSコンポーネント

/* Button.css */
.container {
   margin: 0 auto;
}

.container .button {
   background-color: #996;
   color: #fff;
}

.container .button:hover {
   background-color: #ff6;
}

※例なのでけっこう雑です


基本的にはJS(X)と
CSSの範囲が一致。


メリット

  • スタイルが見つけやすい
  • 再利用性が高まる
  • 捨てやすい

参考リンク: コンポーネント時代の CSS 設計


とは言え、今までもHTMLとSCSSはパーシャル化して対応させていた。
我々にとって、コンポーネントCSSは真新しいことではないかもしれない。


2. グローバルCSS
BEM、
そしてローカルCSS


CSSの問題点


CSS Modules - Welcome to the Future- より引用


基本的には全てのCSSはグローバルである


スタイルの影響範囲が分からない


「なぜかスタイルが効きません😫」


かつての対応策


悩まないコーディングをしよう! OOCSS,SMACSSを用いた、読みやすくてメンテナブルなCSS設計(Sass対応)
より引用


命名規則で対応していた

<div class="block">
    <div class="block__element"></div>
    <div class="block__element--modifier"></div>
</div>
<div class="block--modifier">
    <div class="block--modifier__element"></div>
</div>

BEMという命名規則とSass 3.3の新しい記法より引用


我々の対応

命名規則での対応は、我々が通らなかった道。
bodyクラスで対応していた。

// application.scss
body.account {
  @import "page/account.scss";
}

body.setting {
  @import "page/setting.scss";
}

CSSは<body/>単位でローカル化される。


ローカルCSS


css-loader

webpackのcss-loaderをモジュールモードで使えば、以下のCSSが

// common.css
.button {
  // here comes style
}

以下のように変換される。

// application.css
.common-button-1DYla {
  // here comes style
}

モジュールモードをON

CSS Modulesは、css-loaderのモジュールモードをtrueにした状態。

// webpack.config.js
const cssLoaderQueryObj = {
  modules: true, // ← これ
  sourceMap: isDevelopment ? true : false, 
  localIdentName: '[name]-[local]-[hash:base64:5]',
  importLoaders: 1 // https://www.npmjs.com/package/postcss-loader#integration-with-css-modules
}

クラス名

[ファイル名]-[クラス名]-[ハッシュ値]


  • BEMのように人力で行っていたローカル化を、webpackが自動で行ってくれる。いわば、ローカルCSSのオートメーション化。

  • クラス名の衝突を開発者が気にする必要がなくなった。

  • ちなみに、セレクタにIDやHTMLのタグ名を使うことはできるけど、ローカル化はされない。

参考リンク:[意訳]グローバルCSSの終焉


ただし、css-loaderの仕組みは、<style />タグの動的生成。
→レンダリング時にスタイリングのラグが発生しうる。

そこで、ExtractTextPluginを使う。具体的には、コンパイル時にローカル化済みのCSSファイルを生成する。


ExtractTextPluginコンフィグ

// webpack.config.js
module: {
  loaders: [
    {
      test: /.css$/,
      loader: ExtractTextPlugin.extract([`css?${cssLoaderQuery}`, 'postcss'])
    }
  ]
},
plugins: [
  new ExtractTextPlugin("[name].css") // output CSS
]

これで<style/>、というか新しいCSSファイルが静的に生成される。これをHTMLから読み込む。


これでローカルCSSを静的に読み込むことができる


JS(X)との整合性


静的に生成されるCSSファイルでは、クラス名は既にローカル化されている

.common-button-1DYla {
  // here comes style
}

なので、JSX側で

<button className={button} />

としても、マッチせず、スタイルがあたらない。なので、JSにはローカル化されたクラス名を読み込む必要がある。


とは言えカンタン。CSS Modulesのもう1つの機能として、ローカル化したクラス名のオブジェクトを渡してくれる。

なので、まずはcssをimportして、そのプロパティであるクラス名をclassNameに割り当てる。

import buttonStyles from '../stylesheets/Button.css'

<button className={buttonStyles.button} />
// buttonStylesオブジェクトにクラス名がぶらさがっている。

複数クラス名の同時指定については、もう1工夫が必要。
cf. classnames-loader


cssnext



cssnextとは

未来のCSSを先取りしたもの。策定予定の仕様が盛り込まれている。


CSSファイルでネストが使える(嬉しい)


Sassとは若干シンタックスが異なる

.container {
  & .button {
    color: #fff; 
  }
}

ネストするには、 が必要。


脱Sassしてみる


脱Sass

  • Sassはオールインワン的。便利だけど、機能ありすぎ感。
  • Ruby製のツールなので、フロント的になんだか落ち着かない感。
  • node-sassとか、lib-sassとか、よく分からない…。

というわけで、脱Sassして、cssnextを使ってみることに。


PostCSS


cssnextはそのままでは使えない。ブラウザが読める形式に変換する必要がある。

そのためにPostCSSを使う。
より具体的に言うと、PostCSSのpostcss-cssnextを使う。

逆に言うと、それ以外のPostCSSプラグインはとりあえず使わない方向で。(オレオレCSS化するのを防ぐ)

参考リンク: PostCSSとcssnextで最新CSS仕様を先取り!


cssnextとPostCSSはES201*とbabelの関係


ここまでのwebpackコンフィグを抜き出すとこんな感じ。

module: {
  loaders: [
    {
      test: /.js?$/,
      loader: 'babel',
      exclude: /node_modules/
    },
    {
      test: /.css$/,
      loader: ExtractTextPlugin.extract([`css?${cssLoaderQuery}`, 'postcss'])
    }
  ]
},
postcss: () => {
  return [
    postcssCssnext()
  ]
},
plugins: [
  new ExtractTextPlugin("[name].css") 
]

※ cssローダーのクエリは外出ししている。 ※ ExtractTextPluginの引数の[name]はentry依存。


reset.css

いわゆるリセットCSS的なものも使っている。
ただし、これらはグローバルであってほしいものなので、ローカル化のプロセスをパスしてwebpackのバンドルに直に混ぜている。。

entry: {
  application: [
    './app/frontend/stylesheets/reset.css', // ← リセットCSS
    './app/frontend/javascripts/application'
  ]
},

生成後のCSSファイルの先頭に定義されるように、./app/frontend/javascripts/applicationよりも前に指定している。


おしまい


(存外読まれているので追記)


話の方向性としては、以下のようになりました。

  • Sassとのメリット・デメリットの比較をもっと詳しく行おう。
  • そもそもネストって必要なのか?ネストが不要ならばcssnextは不要化する。
  • デザインの流動性にコンポーネントの世界は耐えられるのか。

そんなわけで、そのうちまた続きを書くかもしれません。