😸

Atomic CSS-in-JSとは何か

2023/06/05に公開

はじめに

Atomic CSSとCSS-in-JSの両方を取り入れた概念であろうものとして「Atomic CSS-in-JS」というワードがある。これが具体的に何を指しているのか不明瞭なので、整理してみる。

https://sebastienlorber.com/atomic-css-in-js

Atomic CSSとは何か?

まずはAtomic CSSが何かを整理する。

Atomic CSSとは何か。それはOOCSSSMACSSといったCSSの設計手法の1つになるけど、CSS-TricksのAtomic CSSについての記事によると、以下のように

Atomic CSS is the approach to CSS architecture that favors small, single-purpose classes with names based on visual function.

単一の視覚的な機能に応じたクラスを優先的に扱うCSSの設計手法のような説明がある。
これと同じものを指すワードとして、Functional CSSやImmutable CSS、Utility First CSSのようなものがあるけども、Atomic CSSはこれらよりも極端なアプローチになる。
初めてAtomic CSSについて言及されたという記事から引用すると、Atomic、つまりそれ以上分解不可能なレベルまでに分解すると単一のスタイルに分解されて、再利用性を最大化できるメリットが得られることについて記述がある。

To break down styles into irreducible units, we can map classes to a single style, rather than many. This will result in a more granular palette of rules, which in turn improves reusability.

ただ、このようなAtomic CSSの厳格さに従っているライブラリなどがあるかというとわからないし、Functional CSS、Utility First CSSなどと言葉を明確に使い分けがされていないケースもあるかもしれない。

なお、Atomicというワードが入っていてもAtomic Designとは関係がない。

Pros/Cons

Atomic CSSのPros/Consとしては、以下のようなものがよく挙げられている。

  • Pros
    • CSSのサイズが肥大化しづらい
    • クラスの命名に時間を取られない
    • ポータビリティ性が高い
  • Cons
    • HTMLが肥大化しやすい
    • 作成済みのクラス名の命名規則について学ぶ必要がある
    • 最初に全てのクラスを用意することになるが、使用されることのないクラスが大量に含まれている可能性も

単一のスタイル毎にクラスを用意し切るとそのクラスをHTML側で利用すればよいので、CSSをその後追加・修正する必要性が生じにくく、CSSのファイルサイズは肥大化しづらい。けれどもその反対にHTMLへ記述するクラスの量は多くなってHTMLのサイズは肥大化しやすい。
また、作成されたクラス名にはそのクラス名を決めた開発者による一定のルールを反映した命名になり(例えば.p-1.fw-600のような)、CSS自体とは別にその命名規則について他の開発者が把握する必要性を生む。
一方で、その定義済みのCSSをそのままさまざまなアプリケーションへそのまま持ち込んで再利用可能にするものであり、可搬性が非常に高い特徴を持っている。

総じて、スタイリングにおける複雑性をCSSではなくHTMLに持ってくるアプローチとなる。

CSS-in-JSとは何か

こちらは、その名前のままのではあるけどJavaScriptにCSSを記述するもので、2014年にReact: CSS in JS - Speaker Deckで登場していて、以降さまざまなライブラリが開発されている。
認知度が高いものだとstyled-componentsEmotionなどがあり、近年だとvanilla-extractのようなビルド時にCSSを生成するゼロランタイムのライブラリが登場してきている。

https://2022.stateofcss.com/ja-JP/css-in-js/

Pros/Cons

実装がさまざまあって、それによってPros/Consに違いがあるけども、ランタイムCSS-in-JSでのPros/Consであげるとしたら以下のようなものがある。

  • Pros
    • スコープが限定される
    • 柔軟性が高い
  • Cons
    • パフォーマンスへの懸念

CSSにおける全てがグローバルスコープという問題に対しての解決策として、コンポーネントのローカルスコープに閉じたスタイリングが可能になるというのが大きい利点としてあげられる。JavaScriptで記述できることによって状況に応じてスタイリングを柔軟に変えることができて、また変数の共有など含めてCSSでそのまま記述するよりも強力な部分がある。
さらに、関連性の強いHTMLとCSSをコロケーションすることが可能になるので、メンテナビリティなどの面で大きなメリットがある。TypeScriptであれば型安全性の面でも恩恵を受けられる。
一方で、ランタイム上でのスタイル追加によるオーバヘッドが発生することにより、画面にスタイリングを反映するまでの時間を要することになる。また、CSS-in-JSのライブラリがダウンロードするファイルサイズに追加となったり、それ自体がCSSを解析する時間も必要となるなど、パフォーマンス上の懸念をいくらか生む可能性がある。

このような従来のCSS-in-JSのランタイムにおけるオーバヘッドを解消するアプローチとして、ビルド時にCSSを生成するゼロランタイムCSS-in-JSが登場していて、ライブラリとしてはlinariaや先に挙げたvanilla-extractなどがある。

加えて、SSRにおいて技術的を引き起こす可能性がある。以下は、emotionにおいての「SSR」でのissue検索結果。

https://github.com/emotion-js/emotion/issues?q=is%3Aissue+is%3Aopen+SSR

Atomic CSS-in-JSとは何か

Atomic CSS-in-JSとは、CSS-in-JSにおいて生成するCSSがAtomic CSSの状態であるものを指すCSS-in-JSの実装詳細のこと。

クラス命名規則から解放される

例えば従来のCSS-in-JSライブラリ(ここではstyled-components)でボタンコンポーネントをレンダリングすると、以下のように1つのクラスで宣言がまとまっているが、

jsx
const Button = styled.button`
  background-color: #666;
  color: #fff;
`;

export default function App() {
  return <Button>btn</Button>;
}
html
<button class="sc-gsFSXq EvUNZ">btn</button>
css
.EvUNZ {
    background-color: #666;
    color: #fff;
}

Atomic CSS-in-JSのライブラリ(ここではgriffel)においては以下のように生成されるCSSが宣言毎に個別のクラスで分けられている。

jsx
const useClasses = makeStyles({
  button: { backgroundColor: "#666", color: "#fff" },
});

export default function App() {
  const classes = useClasses();

  return <button className={classes.button}>btn</button>;
}
html
<button class="___1iprm3f_0000000 f15s8k15 f1b8mhjz">btn</button>
css
/* Atomicなクラスが自動的に生成される */

.f1b8mhjz {
    color: rgb(255, 255, 255);
}

.f15s8k15 {
    background-color: rgb(102, 102, 102);
}

注目するべき点としては、Atomic CSSのConsとして挙げた一定のクラス命名規則から解放されることがある。
コンポーネントで利用する変数名はもちろん必要だけど、これはコンポーネントローカルで閉じたものになると思うので、広いスコープで合意が必ず必要なものではない(変数名の命名規則がある場合には従う必要があるけども、それは全てに共通する)。

CSSのサイズの抑制

従来のCSS-in-JSであればコンポーネントに対するスタイリングがあって、それに応じてクラスは生成される。以下の例だとButtonコンポーネントとHeadingコンポーネントで同じルールセットが記述されているが、CSSクラスはそれぞれコンポーネント毎に別となる。

jsx
const Button = styled.button`
  background-color: #666;
  color: #fff;
`;

const Heading = styled.h1`
  background-color: #666;
  color: #fff;
`;

export default function App() {
  return (
    <div>
      <Heading>heading</Heading>
      <Button>btn</Button>
    </div>
  );
}
html
<div>
  <h1 class="sc-gEvEer hwyBnD">heading</h1>
  <button class="sc-aXZVg efyjdz">btn</button>
</div>
css
/* 全く同じルールセットを持つクラスが重複して作られることに */

.hwyBnD {
    background-color: #666;
    color: #fff;
}

.efyjdz {
    background-color: #666;
    color: #fff;
}

そのため上記のように全く同じスタイルのルールセットであっても個別にクラスが作られることになる。

一方、Atomic CSS-in-JSにおいてはユニークな宣言に対してクラス生成するだけなので、ユニークな宣言毎に個別のクラスが作られる。

jsx
const useButtonClasses = makeStyles({
  button: {
    backgroundColor: "#666",
    color: "#fff",
  },
});

const Button = () => {
  const classes = useButtonClasses();

  return <button className={classes.button}>btn</button>;
};

const useHeadingClassess = makeStyles({
  heading: {
    backgroundColor: "#666",
    color: "#fff",
  },
});

const Heading = () => {
  const classes = useHeadingClassess();

  return <h1 className={classes.heading}>heading</h1>;
};

export default function App() {
  return (
    <div>
      <Heading />
      <Button />
    </div>
  );
}
html
<div>
  <h1 class="___1iprm3f_0000000 f15s8k15 f1b8mhjz">heading</h1>
  <button class="___1iprm3f_0000000 f15s8k15 f1b8mhjz">btn</button>
</div>
css
/* 重複せずユニークな宣言毎にAtomicなクラスが作られる */

.f1b8mhjz {
    color: rgb(255, 255, 255);
}

.f15s8k15 {
    background-color: rgb(102, 102, 102);
}

コンポーネントが増えて同じ宣言が出てきても、その宣言がユニークである場合にのみクラスが増加することになる。
この簡易な例では差異が見えないけども、規模の大きいアプリケーションになるとこのAtomic CSSの特性の恩恵を受けて、CSSのサイズを抑制することができる。

まとめ

Atomic CSSおよびCSS-in-JSにおける特性や課題を確認して、Atomic CSS-in-JSがその両面の良い面を保ちつつ、いくつかの課題解決に繋がるアプローチであることを確認できたかと思う。
CSS-in-JSのライブラリにおいては今後AtomicなCSSを生成できることが必須の機能になり得るだろうけど、現時点では技術選定時に1つのポイントとして見ておくと良いかもしれない。

参考

GitHubで編集を提案

Discussion