CSS
webpack
postcss
cssnext
css-modules

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

More than 1 year has passed since last update.

社内で新しいドメインを設立するにあたり、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


68747470733a2f2f7765627061636b2e6769746875622e696f2f6173736574732f6c6f676f2e706e67.png



何が変わるのか



  • 我々の今までのスタイリング


    • 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の問題点


Screen Shot 2016-08-18 at 14.15.01.png

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



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



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



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



かつての対応策


Screen Shot 2016-08-18 at 14.17.52.png

悩まないコーディングをしよう! 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


Screen Shot 2016-08-18 at 15.13.27.png



cssnextとは

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

Screen Shot 2016-08-18 at 15.15.32.png



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は不要化する。

  • デザインの流動性にコンポーネントの世界は耐えられるのか。

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