$30 off During Our Annual Pro Sale. View Details »

AbemaTVにおけるCSS is too fragile問題に対する解 / Solution of "CSS is too fragile" by AbemaTV

AbemaTVにおけるCSS is too fragile問題に対する解 / Solution of "CSS is too fragile" by AbemaTV

リファクタリングをしていたらCSS Modulesを使っていたにも関わらずCSSが壊れてしまった私達は、CSS ModulesがCSSの特性・我々のプロダクトの構成や開発方針に合わないと判断し「BEMベースのクラス名設計 + PostCSS」へ移行を進める方針を決めました。 これらの背景や、どうやって移行を進めたのか、また移行したあとの評価について話しました。

Shota Kubota

May 18, 2019
Tweet

More Decks by Shota Kubota

Other Decks in Programming

Transcript

  1. AbemaTVにおける
    CSS is too fragile問題に対する解
    @kubotashota

    View Slide

  2. この内容は当サービスでの解であり
    他サービスには当てはまらない
    場合があります

    View Slide

  3. 1. 前提
    2. どうしたか
    3. 対応後の評価
    4. 教訓
    話すこと

    View Slide

  4. 前提
    1

    View Slide

  5. AbemaTVについて
    ”無料で楽しめるインターネットテレビ局 ”
    として次の特徴を持っている
    ● TVのようにチャンネルを合わせると
    配信中の番組が流れる
    ● 見逃した番組や様々な作品を見られる
    ビデオオンデマンドサービスの提供

    View Slide

  6. もともとのCSSの構成
    ● css-loaderを使ってクラス名のユニーク化やファイルの結合をしていた
    ● Reactのコンポーネントのインターフェースとして
    className propsを定義していた
    ○ 上位層のコンポーネントで下位層のコンポーネントに対して
    classNameを渡すことでスタイルの上書きを可能にしていた
    ● UIコンポーネントはAtomic Designの語彙を元に分割している

    View Slide

  7. 元のCSSの構成: ディレクトリ構造とコード例
    import React from 'react';
    import classNames from 'classnames';
    import styles from './Button.css';
    export const Button = ({
    className, disabled, onClick
    }) => {
    className = classNames(
    className, styles.btn
    );
    return (
    className={className}
    disabled={disabled}
    onClick={onClick}
    >
    {children}

    );
    };
    @import "vars";
    .btn {
    border-radius: var(--radius);
    font-size: 100%;
    padding: .75em 3em;
    text-decoration: none;
    }
    .btn:hover {
    opacity: var(--opacity);
    }
    .btn:disabled {
    opacity: .2;
    }
    src/components
    ├── atoms
    │ └── Button
    │ ├── index.js
    │ ├── Button.jsx
    │ └── Button.css
    ├── molecules
    └── organisms

    View Slide

  8. 変更前後のディレクトリ構造
    Before
    /src
    ├── /container/foo/A
    ├── /actions/foo/A
    ├── /dispatchers/foo/A
    ├── /stores/foo/A

    ├── /container/bar/B
    ├── /actions/bar/B
    ├── /dispatchers/bar/B
    └── /stores/bar/B
    After
    /src
    ├── /foo/container/A
    ├── /foo/actions/A
    ├── /foo/dispatchers/A
    ├── /foo/stores/A

    ├── /bar/container/B
    ├── /bar/actions/B
    ├── /bar/dispatchers/B
    └── /bar/stores/B
    ● テレビ機能しかなかった
    初期構成の負債の返却
    ● 特定の機能に閉じた変更
    をしやすくする

    View Slide

  9. デグレ: ディレクトリ変更で
    スタイルが壊れる

    View Slide

  10. デグレ: ディレクトリ変更でスタイルが壊れる
    ● importの記述順序を変えるだけでルールセットの順序が変わりうる
    ○ CSS Modulesのモジュール探索の順番はJSと同様に深さ優先探索
    ● classNameの上書きがこの順序に影響しうる
    ○ モジュールの依存グラフの到達順序によっては最終的なCSSの
    ルールセットの順序は変わる

    View Slide

  11. スタイルが壊れる例
    A
    B C
    D E
    A
    B C
    D E
    BCD間に記述順序に依存するスタイルがあると崩れる
    1
    2
    3
    4
    5
    1
    4
    2
    3
    5
    変更前 変更後

    View Slide

  12. スタイル崩れ その1

    View Slide

  13. スタイル崩れ その2
    崩れている例
    正常な例

    View Slide

  14. スタイル崩れ その2
    崩れている例
    正常な例
    スタイルの定義順が入れ替わっていた
    /* 入力フォーム側のボタンのスタイル定義 */
    .btn {
    padding: 12px 24px;
    }
    /* ボタン側のスタイル定義 */
    .btn {
    padding: .75em 3em;
    }
    段差がある
    段差がない

    View Slide

  15. なぜディレクトリ構造の変更でスタイルが崩れたか
    ● ディレクトリ構造の変更に応じてimportの順序を変えた結果
    モジュールの探索の順番が変わり、生成されるCSSの
    ルールセットの定義順が変わった
    ● 上位のコンポーネント(Containerなど)で下位コンポーネントの
    スタイル定義を上書きしているところが多くあった
    ○ 汎用的なコンポーネントを作ろうとした結果、
    変更に対して開かれすぎたコンポーネントができていた

    View Slide

  16. 私たちが欲しかったもの
    ● CSS Modulesベースの設計と似たもの
    ● リファクタリングを阻害しないこと
    ● ライブラリ独自の機能に対する依存が少ない

    View Slide

  17. どうしたか
    2

    View Slide

  18. BEM + PostCSSへ移行
    ● BEMベースのクラス名の命名規則
    ○ .アプリケーションドメイン -コンポーネント名__コンポーネント内の要素 --修飾子
    ■ 例: .video-PlayerContainer__screen
    ○ grepのためにクラス名を JavaScriptで動的に構築するのは避ける
    ● postcss-importを使ってCSSが展開されるようにした
    ○ コンポーネントのルートディレクトリ内に root.cssを用意して
    その中でCSSをimportする

    View Slide

  19. BEM + PostCSSへ移行
    Before: CSS Modulesベース
    @import "vars";
    .btn {
    border-radius:
    var(--radius);
    font-size: 100%;
    padding: .75em 3em;
    text-decoration: none;
    }
    .btn:hover {
    opacity: var(--opacity);
    }
    .btn:disabled {
    opacity: .2;
    }
    After: BEM + PostCSSベース
    .com-a-Button {
    border-radius:
    var(--radius);
    font-size: 100%;
    padding: .75em 3em;
    text-decoration: none;
    }
    .com-a-Button:hover {
    opacity: var(--opacity);
    }
    .com-a-Button:disabled {
    opacity: .2;
    }
    root.css
    @import './vars.css';
    @import './component/atom.css';
    @import './component/molecules.css';
    @import './component/organisms.css';
    /* template */
    @import './featA/featA.css';
    @import './featB/featB.css';
    /* page */
    @import './page1/page1.css';
    @import './page2/page2.css';

    View Slide

  20. 対応後の評価
    3

    View Slide

  21. スタイル適用をしやすくなった
    ● CSS Modulesのようにルールセットの並び順がブラックボックスに
    ならなくなり、CSSのimport順を変えれば意図通りのスタイルが
    適用されるようになった
    ● クラス名のユニーク化をツールに任せるのではなく
    開発者側でやるようにしたことでブラウザ拡張などで
    CSSを上書きしやすくなった
    ○ Stylus(ブラウザ拡張)を使ってデザイナーが
    プロトタイピングをすることも容易になった

    View Slide

  22. CSS Modulesよりは考えることが増えた
    ● BEM + PostCSSではクラス名の規約を作る必要が増えた
    ● クラス名の規約に沿っているかレビューで見る必要が増えた
    ● 規約をチームメンバーに浸透する必要が増えた
    ● チームメンバーに規約が浸透すればレビューで指摘することは
    そんなにない
    ● 生成後のCSSを想像しやすくなった利点のほうが大きい

    View Slide

  23. パフォーマンスはどうなのか?
    ● CSSのサイズはデグレ対応の前後で35KB→36KBと誤差の範囲で増加
    ● CSS ModulesからBEM + PostCSSへ移行して
    ページの表示速度が大きく遅くなってはいない
    ● 移行を進めていく上で将来的にCSSのサイズが増えすぎるようだったら
    デバイスやページ単位でCSSを分けることもできる

    View Slide

  24. 他の選択肢と選ばなかった理由

    View Slide

  25. 他の選択肢と選ばなかった理由
    ● どうにかしてCSS Modulesを使い続けたりCSS in JSを使ったりする
    ○ 標準のCSSの書き方を保ちやすい、ビルドも複雑にならないという点で
    BEM + PostCSSのほうがのちのち捨てやすいと判断
    ○ 標準仕様から外れるような PostCSSのプラグインを使わないようにしている
    ● ディレクトリ構造の変更後にモジュールの探索順序を
    変えないようにするためimport宣言の順序を変えない
    ○ import宣言の並び順に一貫性がなくなる
    ○ それはリファクタリングしたと言えるのか?

    View Slide

  26. 教訓
    4

    View Slide

  27. 教訓
    ● CSS ModulesやCSS in JSは使い方を気をつけないと
    ふとしたときに壊れる可能性がある
    ● サービスの段階によって取り入れるものを変えたほうが
    改善をしやすくなる場合がある

    View Slide

  28. Thank you!
    Twitter: @kubotashota

    View Slide