Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

前提 1

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

元の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 ( {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

Slide 8

Slide 8 text

変更前後のディレクトリ構造 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 ● テレビ機能しかなかった 初期構成の負債の返却 ● 特定の機能に閉じた変更 をしやすくする

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

スタイル崩れ その1

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

どうしたか 2

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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';

Slide 20

Slide 20 text

対応後の評価 3

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

教訓 4

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Thank you! Twitter: @kubotashota