Free-Style のススメ ~ CSS Modules は解決策ではない

# 最初にまとめ: Free-Style とは

CSS in JS の一種
https://github.com/blakeembrey/free-style

## CSS in JS の長所はそのままに

* スタイルをローカルスコープで定義可能
* JS の仕組みによる依存性解決 (CommonJS, Require.js, ES modules)
* 使われていないスタイルの自動削除 (UglifyJS 等)
* スタイル-スタイル間、および JS-スタイル間の定数の共有が容易
* JS による柔軟なスタイルの拡張・再利用が可能

## CSS in JS が苦手としていた点を改善

* style 属性に指定可能な内容に限らず、 CSS が提供するすべての機能を利用可能
  * 疑似クラス
  * 疑似要素
  * メディアクエリ

# 前置き: CSS in JS と CSS Modules

## 前置きの前置き

この記事は、 :rose: の emoji でおなじみ (?) の @basarat さんの以下の記事を読んで「へえ」と思って色々調べた結果をまとめたものです。
煽り気味のタイトルも拝借させていただきました。

https://medium.com/@basarat/css-modules-are-not-the-solution-1235696863d6#.jgvyjjkg0

## CSS in JS

* 元ネタ: https://speakerdeck.com/vjeux/react-css-in-js
* 日本語の解説記事: http://qiita.com/koba04/items/0e81a04262e1158dbbe4
* 「ルールセットがグローバルスコープ」、「依存管理が困難」など、 CSS が抱える問題点を解決するものとして提起

### 使用例

※ http://qiita.com/koba04/items/0e81a04262e1158dbbe4 より引用

```js
var style = {
  container: {
    backgroundColor: "#ddd",
    width: 900
  }
}

var Container = React.createClass({
  render() {
    return <div style={style.container}>{this.props.children}</div>;
  }
});
```

### CSS in JS の弱点

* **style 属性に指定できないようなスタイルを定義することが不可能、ないし困難** (疑似クラス、疑似要素、メディアクエリ)
* JS でがんばっている (`:hover` を mouse event で実現するなど) ライブラリもある
  * もっとも有名なのは [Radium](http://stack.formidable.com/radium/)
  * JS で CSS のすべての機能を再現できるわけではなく、限界がある

## CSS Modules

* https://github.com/css-modules/css-modules
* CSS ファイルを JS から import して使う
  * 実際には、 CSS ファイルからビルド時に CSS と JS が出力され、その JS の方が import される（詳細は使用例を参照）
* 実体は CSS なので、 **CSS で実現できることはすべて実現可能**
* ルールセットがローカルスコープだったり、スタイルの再利用ができたり、 CSS in JS で実現していたこともある程度は実現可能

### 使用例

※ https://github.com/css-modules/css-modules/blob/master/docs/pseudo-class-selectors.md より引用

```css
/* component/text.css */
.text {
  color: #777;
  font-weight: 24px;
}
.text:hover {
  color: #f60;
}
```

上記の CSS ファイルを webpack の [css-loader](https://github.com/webpack/css-loader) でビルドすると、

```css
._23_aKvs-b8bW2Vg3fwHozO { 
  color: #777;
  font-weight: 24px;
}
._23_aKvs-b8bW2Vg3fwHozO:hover {
  color: #f60;
}
```

みたいな CSS と、

```js
exports.locals = {
  text: "_23_aKvs-b8bW2Vg3fwHozO"
}
```

みたいな JS が出力されるので、

```js
/* component/text.js */
import styles from './text.css';

import React, { Component } from 'react';

export default class Text extends Component {

  render() {
    return (
      <p className={ styles.text }>Text with hover</p>
    );
  }

};
```

のように import して使うことができる (class 名の hash 化の仕方は適当です)。

### CSS Modules の弱点

* 定数の共有や、スタイルの拡張などは、 CSS in JS のように JS の世界でできたほうがやりやすそう (慣れの問題かもしれない)
* **CSS と JS の間での定数の共有ができない**

# そこで、 Free-Style

* スタイル定義の実体が CSS である点は、 CSS Modules と同じ
  * **CSS が提供するすべての機能を利用可能**
* CSS Modules が CSS を入力として CSS と JS を出力するのに対し、 Free-Style は JS を入力として CSS と JS を出力する
  * 開発者が書くのは JS のコードなので、 **CSS in JS 的なメリットはすべて享受できる**
  * もちろん、 CSS に変換される JS コードと、通常の JS のコード間での定数の共有も可能
* React 等特定のライブラリに依存しない
  * 例えば Radium は React の使用が前提になっている
  * [React Free Style](https://github.com/blakeembrey/react-free-style) という React 向け拡張も用意されていたりはする

### 使用例

※ https://github.com/blakeembrey/free-style より引用、コメントを一部改変

```js
var FreeStyle = require('free-style')

// Create a container instance.
var Style = FreeStyle.create()

// Register a new, uniquely hashed style.
var STYLE = Style.registerStyle({
  backgroundColor: 'red'
}) //=> STYLE には "f14svl5e" が割り当てられ、 Style オブジェクトには ".f14svl5e { background-color: 'red' }" という CSS が登録される

// Inject a `<style />` element into the `<head>`.
Style.inject() // <style>.f14svl5e { background-color: 'red' }</style> が <head> に出力される

// Figure out how to render the class name after registering.
React.render(
  <div className={STYLE}>Hello world!</div>,
  document.body
)
```

#### 書き方色々

疑似クラス、疑似要素、メディアクエリ等

```js
var
    style1 = style.registerStyle({
        position: 'absolute',
        top: -42,
        '&:before': { // 疑似要素
            content: `'»'`,
            fontSize: 28,
            color: '#d9d9d9',
            padding: '0 25px 7px'
        },
        '&:checked:before': { // 疑似クラス + 疑似要素
            color: '#737373'
        },
        '@media screen and (-webkit-min-device-pixel-ratio:0)': { // メディアクエリ
            background: 'none',
            top: -56
        }
    }),
    style2 = style.registerStyle({
    	position: 'relative',
        fontSize: 24,
        borderBottom: '1px dotted #ccc',
        '&.editing': { // 他のクラスとの組み合わせ
            borderBottom: 'none',
            padding: 0
        },
        '&:last-child': { // 疑似クラス
            borderBottom: 'none'
        },
        '&.editing .edit': { // 子孫セレクタの指定
            display: 'block'
        }
    });
```

上記は以下のような CSS に変換されます (class 名の hash 化ロジックは適当)

```css
/* style1 */
.f1n85iiq {
  position: absolute;
  top: -42px;
}
.f1n85iiq:before {
  content: '»';
  font-size: 28px;
  color: #d9d9d9;
  padding: 0 25px 7px;
}
.f1n85iiq:checked:before {
  color: #737373;
}
@media screen and (-webkit-min-device-pixel-ratio:0) {
  .f1n85iiq {
    background: none;
    top: -56px;
  }
}

/* style2 */
.fk9tfor {
  position: relative;
  font-size: 24px;
  border-bottom: 1px dotted #ccc;
}
.fk9tfor.editing {
  border-bottom: none;
  padding: 0;
}
.fk9tfor:last-child {
  border-bottom: none;
}
.fk9tfor.editing .edit {
  display: block;
}
```

ポイントは、

* 変換後の class 名は `&` で表現する
* 生で書いた class 名 (上記の例では `.editing`, `.edit`) は hash 化されずにそのまま使われるため、グローバルな class となってしまう
  * ただし、 `style.registerStyle()` の中でしか使わない (素の CSS には書かない) ことを徹底すれば、 hash 化された (`fk9tfor` のような) class 名との組み合わせにより、実質的にローカルスコープにしか影響を及ぼさないと考えることもできる

#### TodoMVC の実装例

https://github.com/kimamula/ts-react/tree/feature/free-style

* CSS 部分をすべて Free-Style (React Free Style) で実装した、 TodoMVC アプリケーション
* `git clone` してサクッと動かせるはずなので、よかったら試してみてください

### Free-Style の弱点

* 今のところそんなに流行っていないので、乗っかってしまって大丈夫か不安
* CSS Modules との比較でいうと、 CSS Modules は CSS で書くので、「いつでも引き返せる」みたいな安心感は CSS Modules の方が強いと思う 

### 類似ライブラリ

JSS: https://github.com/jsstyles/jss

* Free-Style と同様、 JS を入力として CSS と JS を出力する
* GitHub の star 数的には、 JSS に軍配
* 両者の違いについては [Free-Style のこの issue](https://github.com/blakeembrey/free-style/issues/16) での議論が参考になりそう
  * 簡単に言うと、 Free-Style はシンプル、 JSS は多機能
  * 自分は上記 issue での Free-Style の @blakeembrey さんの指摘通り、 JSS の機能は過剰に感じたため、 Free-Style の方が好き

# 感想

実際にコードを書いてみて、 Free-Style なら CSS とうまくやっていけそう、という実感があったので、是非実戦で使ってみたいです。
あとはチームの他のメンバーが気に入ってくれるか次第。