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」へ移行を進める方針を決めました。 これらの背景や、どうやって移行を進めたのか、また移行したあとの評価について話しました。

Cb0e411d88a1bcd1562890c816970a8e?s=128

Shota Kubota

May 18, 2019
Tweet

Transcript

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

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

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

  4. 前提 1

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

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

    • UIコンポーネントはAtomic Designの語彙を元に分割している
  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 ( <button className={className} disabled={disabled} onClick={onClick} > {children} </button> ); }; @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
  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 • テレビ機能しかなかった 初期構成の負債の返却 • 特定の機能に閉じた変更 をしやすくする
  9. デグレ: ディレクトリ変更で スタイルが壊れる

  10. デグレ: ディレクトリ変更でスタイルが壊れる • importの記述順序を変えるだけでルールセットの順序が変わりうる ◦ CSS Modulesのモジュール探索の順番はJSと同様に深さ優先探索 • classNameの上書きがこの順序に影響しうる ◦

    モジュールの依存グラフの到達順序によっては最終的なCSSの ルールセットの順序は変わる
  11. スタイルが壊れる例 A B C D E A B C D

    E BCD間に記述順序に依存するスタイルがあると崩れる 1 2 3 4 5 1 4 2 3 5 変更前 変更後
  12. スタイル崩れ その1

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

  14. スタイル崩れ その2 崩れている例 正常な例 スタイルの定義順が入れ替わっていた /* 入力フォーム側のボタンのスタイル定義 */ .btn {

    padding: 12px 24px; } /* ボタン側のスタイル定義 */ .btn { padding: .75em 3em; } 段差がある 段差がない
  15. なぜディレクトリ構造の変更でスタイルが崩れたか • ディレクトリ構造の変更に応じてimportの順序を変えた結果 モジュールの探索の順番が変わり、生成されるCSSの ルールセットの定義順が変わった • 上位のコンポーネント(Containerなど)で下位コンポーネントの スタイル定義を上書きしているところが多くあった ◦ 汎用的なコンポーネントを作ろうとした結果、

    変更に対して開かれすぎたコンポーネントができていた
  16. 私たちが欲しかったもの • CSS Modulesベースの設計と似たもの • リファクタリングを阻害しないこと • ライブラリ独自の機能に対する依存が少ない

  17. どうしたか 2

  18. BEM + PostCSSへ移行 • BEMベースのクラス名の命名規則 ◦ .アプリケーションドメイン -コンポーネント名__コンポーネント内の要素 --修飾子 ▪

    例: .video-PlayerContainer__screen ◦ grepのためにクラス名を JavaScriptで動的に構築するのは避ける • postcss-importを使ってCSSが展開されるようにした ◦ コンポーネントのルートディレクトリ内に root.cssを用意して その中でCSSをimportする
  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';
  20. 対応後の評価 3

  21. スタイル適用をしやすくなった • CSS Modulesのようにルールセットの並び順がブラックボックスに ならなくなり、CSSのimport順を変えれば意図通りのスタイルが 適用されるようになった • クラス名のユニーク化をツールに任せるのではなく 開発者側でやるようにしたことでブラウザ拡張などで CSSを上書きしやすくなった

    ◦ Stylus(ブラウザ拡張)を使ってデザイナーが プロトタイピングをすることも容易になった
  22. CSS Modulesよりは考えることが増えた • BEM + PostCSSではクラス名の規約を作る必要が増えた • クラス名の規約に沿っているかレビューで見る必要が増えた • 規約をチームメンバーに浸透する必要が増えた

    • チームメンバーに規約が浸透すればレビューで指摘することは そんなにない • 生成後のCSSを想像しやすくなった利点のほうが大きい
  23. パフォーマンスはどうなのか? • CSSのサイズはデグレ対応の前後で35KB→36KBと誤差の範囲で増加 • CSS ModulesからBEM + PostCSSへ移行して ページの表示速度が大きく遅くなってはいない •

    移行を進めていく上で将来的にCSSのサイズが増えすぎるようだったら デバイスやページ単位でCSSを分けることもできる
  24. 他の選択肢と選ばなかった理由

  25. 他の選択肢と選ばなかった理由 • どうにかしてCSS Modulesを使い続けたりCSS in JSを使ったりする ◦ 標準のCSSの書き方を保ちやすい、ビルドも複雑にならないという点で BEM +

    PostCSSのほうがのちのち捨てやすいと判断 ◦ 標準仕様から外れるような PostCSSのプラグインを使わないようにしている • ディレクトリ構造の変更後にモジュールの探索順序を 変えないようにするためimport宣言の順序を変えない ◦ import宣言の並び順に一貫性がなくなる ◦ それはリファクタリングしたと言えるのか?
  26. 教訓 4

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

  28. Thank you! Twitter: @kubotashota