社内で新しいドメインを設立するにあたり、__CSS Modules, PostCSS, cssnext__を試してみました。
このスライドは、その際の説明に使ったものです。せっかくなので公開します。
「プロトタイプ作成で試してみたけど、みなさんどう思いますか?」くらいの温度感。本番採用が確定したわけではありません。何かお役に立つことがアレば幸いです。
以後、説明に使ったスライド。
おしながき
-
- コンポーネント時代のスタイリング
-
- グローバルCSS、BEM、そしてローカルCSS
-
- CSS Modules、そしてJSXへの割り振り
-
- cssnextと、その書き方
-
- 我々の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>
我々の対応
命名規則での対応は、我々が通らなかった道。
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は不要化する。
- デザインの流動性にコンポーネントの世界は耐えられるのか。
そんなわけで、そのうちまた続きを書くかもしれません。