Edited at

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

More than 3 years have passed since last update.


最初にまとめ: 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


使用例

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

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

    • 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 より引用

/* component/text.css */

.text {
color: #777;
font-weight: 24px;
}
.text:hover {
color: #f60;
}

上記の CSS ファイルを webpack の css-loader でビルドすると、

._23_aKvs-b8bW2Vg3fwHozO { 

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

みたいな CSS と、

exports.locals = {

text: "_23_aKvs-b8bW2Vg3fwHozO"
}

みたいな 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 という React 向け拡張も用意されていたりはする




使用例

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

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
)


書き方色々

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

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 化ロジックは適当)

/* 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 での議論が参考になりそう


    • 簡単に言うと、 Free-Style はシンプル、 JSS は多機能

    • 自分は上記 issue での Free-Style の @blakeembrey さんの指摘通り、 JSS の機能は過剰に感じたため、 Free-Style の方が好き




感想

実際にコードを書いてみて、 Free-Style なら CSS とうまくやっていけそう、という実感があったので、是非実戦で使ってみたいです。

あとはチームの他のメンバーが気に入ってくれるか次第。