ミニマルなSCSSアーキテクチャの導出とITCSS
概要
この記事ではスタイル(CSS)管理上の課題を提示し、その解決策をシンプルに映し出すSCSSアーキテクチャを導出します。
その結果としてITCSSと呼ばれるSCSSアーキテクチャのサブセットが導かれます。
想定読者
- スタイル(CSS)管理に悩まされるフロントエンドエンジニア
前提
現状複雑なページビューを構成するにあたりReactを使用しています。
フルReactですべてのページを構成する前提であれば、CSS ModulesあるいはCSS in JSなどを用いることでCSSに(メカニカルな)スコープを付与できます。
メカニカルなスコープによりスタイルシート管理に秩序をもたらすことは容易なことになります。
あのコンポーネントとこのコンポーネントで命名規則が全く違っていても、スコープという構造で保護されている以上、目くじらを立てる必要はないのです。
(筆者が命名についてどう考えているかは前の記事で示しました。)
そのことに深く同意しそれを常に実行しきる環境であるならば、この記事から得るものは何もありませんので「いいね」して忘れましょう。
現実的なサービスにおいては、常にReactやJavaScriptを使用するという前提を置けないことがあります。
単なる静的ページ(html+css)で構成したいという要求や、そのような既存ページが沢山あるという状況は歴史があるサービスならば自然なのです。
とくに既存ページのリプレースを行うにあたり、破綻しにくい一貫したスタイルポリシーを形成しなければならないという要求は、極めて現実的なものです。
- 通常のSCSSとCSS Modulesの共通化、相互移行ができる状態を保ちたい
- その手続きが明確化でき、連続的に実行できる
- 可能な限りポリシーをミニマルに定式化する
以上のことを目標として、この課題について真面目に考えてみることにしました。
CSS構成の課題点を整理する
サービスの既存のCSSの構成を見ると以下のようになっていました。
- 下記がRailsレイアウトから一括で読み込まれている
- リセットCSS
- (レスポンシブ)レイアウトを実現するベースとなるCSS
- BEMライクのクラスを付与することで利用可能なモノリシックに読み込まれるコンポーネント群
- AtomicDesignに基づくプレフィックスがクラスに付与されている
これによりたとえば.a-buttonといったクラスを付与すれば、そのボタンスタイルが適用されるようになります。
これはページを構成するスピードにのみフォーカスすれば非常に便利な考え方です。
一方でこの構造は時の試練に耐えることができない構造でもあります。
その理由は以下のとおりです。
- Railsレイアウトを通じてコンポーネント群が、レイアウトに暗黙的に依存する
- たとえばレスポンシブレイアウトを前提としないといった要求に対して極めて脆弱である
- 実際そういう要求が発生してしまったからこそ、この設計は始まった
- たとえばレスポンシブレイアウトを前提としないといった要求に対して極めて脆弱である
長期的保守性を前提とすると、本来レイアウトシステムとコンポーネントシステムは疎結合(無関係)に構成しなければなりません。
一方で短期的なページ構成速度を重視すると、密結合にしたほうが良いのです。
ページを速く作れるインセンティブが、アーキテクチャを崩すというメタ構造はとくに意識すべきポイントです。
この調停すべき関係性(=記述性と可読性のトレードオフ)を無視して何度設計しても、CSSはつかの間のナギ節ののち再び崩壊します。
(実際には、HTTP1.1を前提とするとこの設計技法はパフォーマンス観点から正当化されうるのですが、HTTP2以降を前提とするならば、こうする意味は記述速度以外にないです。)
アーキテクチャの前提
設計を進めるには、そのアーキテクチャの構成目的(要求)と利用技術が持つ制約に着目し、その両側からあるべき状態を導出することが重要です。
今回のアーキテクチャを導くための前提(要求と制約)を以下に列挙します。
- レイアウトシステムとコンポーネントシステムは分離する
- ❌ ページはレイアウトを持ち、レイアウトはコンポーネント群を持つ
- ⭕ ページはレイアウト(ベースcss)を持ち、必要なコンポーネントをチョイスできる
- スタイルの利用箇所がわかりやすいような構造にする
- ❌ .a-buttonがどこから注入されているかわからない
- ⭕ .a-buttonは
@use '.a-button'
として参照しているので利用できる
- 可能な限りパフォーマンス上のペナルティを受けにくい構造にする
- Dart Sassの@useのセマンティクスを意識する
- @useを通じてペナルティなく参照できる要素(variable, function, mixin)と実スタイルは分けて管理する
- ❌上記要素と実スタイルを混在させる
- variableを参照するために@useすると(不要な)実スタイルも読み込まれるようになる
- Dart Sassの@useのセマンティクスを意識する
- 再利用するpure SCSSなクラススタイルはBEMを守る
- CSS Modulesのようなメカニカルなスコープがない以上、手動によるスコープが必要
- CSSのクラススタイルは事実上のグローバル変数として取り扱うという意識が重要
- 実際にはCSS Modulesなどが使用できるならそっちを積極的に利用した方がスタイルを深化させやすい
- スコープがメカニカルに保証されていれば、CSS ModulesをBEMに展開し静的ページから参照するようリファクタリングすることは難しくない
- CSS Modulesのようなメカニカルなスコープがない以上、手動によるスコープが必要
- 属人性が強い命名要素を可能な限り排除する
- = Atomic Designをやめる
- ❌人の数だけ仕分け方法が異なりうる(molecules or organisms or etc.)
- ⭕そんな議論が必要になる抽象化は引きさる
- 最初に引き算しなければ、あとは膨れ上がり、そして共通理解を喪失する
- プロセス設計とは、やらないことを明確にしプロセスから明示的に排除すること
- MECEな分類、完全なルールベースで分割する
- これに反した分類は負債の温床になる
- = Atomic Designをやめる
ミニマルなアーキテクチャの導出
以上のようなことを整理してディレクトリ構造として表現すると以下のような3つのディレクトリに綺麗に分割されます。
- tools
- ⭕️ SCSSのvariables, functions, mixins
- ❌ クラス, タグ, 属性スタイル
- これらは参照しても使用しない限り出力されないスタイル
- bases
- (HTML)タグ, 属性スタイルに影響を与えるものを入れる
- ⭕️ (純粋な)タグ, 属性スタイル
body{} a:active{} [aria-controls]{} p{}
- ❌ クラススタイル
- これを入れるとblocksとコンフリクトする可能性がある
- ⭕️ (純粋な)タグ, 属性スタイル
- (HTML)タグ, 属性スタイルに影響を与えるものを入れる
- blocks
- クラススタイルに影響を与えるものを入れる
- ⭕️ クラスが混在するスタイル
.s-button{} a.s-link{}
- ❌ タグ, 属性スタイル
body{} a:active{} [aria-controls]{} p{}
- あるクラススタイルを利用しようとすると<p>に謎のマージンがつくようになる
- ⭕️ クラスが混在するスタイル
- BEM命名規則を採用し、ファイル名をBlock名と一致させる
-
ディレクトリ構造でBlockの一意性を保証する
- s-button.scssには.s-buttonから始まるブロックスタイルのみ入れる
-
ディレクトリ構造でBlockの一意性を保証する
-
s-
プレフィックスをつける- 検索をしやすくするためにつけている(sに意味はない)
- .fooはcssの文脈だとクラススタイルだが、プログラム言語では一般にはメソッドコール
- 意味論は異なるが、シンタックスは同じという状況を事前に回避した方が明瞭である
- .s-fooという呼び出しを認めるプログラム言語は少ない
- クラススタイルに影響を与えるものを入れる
全てのスタイルが、3つのいずれかに客観的に分類可能であることがとても重要です。
そのことは複雑なリファクタリングをやると痛感するわけですが、いちいち経験せずとも納得できる理由づけを残すためにこの記事をかきました。
ITCSSとの関連
このようにして机上で必要な構成を導出(=設計)したわけですが、「これって絶対に似たような構成例がパッケージ化されているはずだな」という感覚がありました。
必然性に基づいて設計するならば、よほど未熟な業界でもなければ前例が必ずあるはずだと考えるわけです。
その観点でいろいろとWeb検索をすると、ITCSSなる設計概念があることを発見しました。
提唱者はCSS WizardryのHarry Robertsという人で国際連合に対する技術コンサルティング経験もある方のようです。
これはCSSの詳細度の概念に基づき、CSSを以下の7つのレベルにわけ厳密に手動管理するための設計技法です。
- Settings
- Tools
- Generic
- Base
- Objects
- Components
- Trumps
机上で導出した構成ではミニマルさを追求して3階層になりましたが、以下のような対応関係になります。
- tools = Settings + Tools
- bases = Generic + Base
- blocks = Objects + Components + Trumps
元々はレイヤを別の名前(tools ← libs, blocks ← parts)で呼んでいましたが、ITCSSに近づけるように名称変更しました。
blocksだけはBEMのBlockの一意性を死守する関係上、それを強調する名称としました。
今のところ3階層でも十分な感じはしていますが、必要に応じレイヤを増やすつもりです。
結論
- 要件に応じて必要な構成を導き出しました
- 導出することによってその構造の必然性が示され、新たな課題が出てきても対応できます
- アイデアを借用するだけでは、応用がききません
- 導出することによってその構造の必然性が示され、新たな課題が出てきても対応できます
- メカニカルに問題解決できる方法があるなら、それを採用したほうがよいです
- CSS Modulesを採用できればこんな面倒な議論は全てスキップできます
- 現実は前提を単純化できないことの方が多いです
- 複雑な条件を机上で解き切り、ルールを最小化する努力が設計には求められます
- 現実は前提を単純化できないことの方が多いです
- CSS Modulesを採用できればこんな面倒な議論は全てスキップできます
参考文献
ITCSSを採用して共同開発しやすいCSS設計をZOZOTOWNに導入した話
ITCSSを採用するまでの問題意識が似通っており、参考になりました。
HTTP/2時代のウェブサイト設計
スライド60ページ目がとくに重要でした。
机上で考えると通信多重度の制限がなければ、アセットバンドリングを推し進めるべき合理的な理由はないはずと考えていましたが、確証が持てなかったので助かりました。
▽ Managing CSS Projects with ITCSS
詳細度を軸として専門家が真面目に整理するとITCSSという構造が導かれます。
レイヤ分割の粒度は異なりますが、似たような構成が導かれていることを確認できたので安心しました。
Discussion