LoginSignup
31
25

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-07-17

最初にまとめ: 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 さんの以下の記事を読んで「へえ」と思って色々調べた結果をまとめたものです。
煽り気味のタイトルも拝借させていただきました。

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 の実装例

  • 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 とうまくやっていけそう、という実感があったので、是非実戦で使ってみたいです。
あとはチームの他のメンバーが気に入ってくれるか次第。

31
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
25