[CSS in JS] Emotion の選定理由👩‍🎤

6 min read

背景

新規で CSS in JS を利用するにあたり、Emotionを選定した。

個人的に選定理由をまとめておきたいので簡潔に書いていく。

CSS in JS ライブラリに求めている機能

CSS in JS のライブラリとして必要とした機能は以下。

  • タグ付きテンプレートリテラル記法が使えること
    • CSS の記法が使えるため既存実装(CSS ファイル)をそのまま生かしやすい
      オブジェクトスタイル記法だとプロパティ名をキャメルケースにする必要がある(e.g.: font-sizefontSize
    • CSS in JS に明るくないメンバーでもシンタックスが異なるという障壁がなく参入しやすい
  • グローバルスタイルが定義できること

CSS in JS ライブラリを選定するための条件

選定条件としては以下。

  • ドキュメントが多いこと
    • 他社事例や著名な UI ライブラリが Emotion を採用している事例が多く観測できた
  • メンテナンスされていること
  • Next.js 上で動作させる予定だったため、styled-components、Emotion などであれば、SWC コンパイラに切り替える選択もできる

Zero-runtime

パフォーマンスの観点で Zero-runtime CSS in JS ライブラリも選択肢のひとつとして挙がった。

API が styled-components や Emotion などの先発ライブラリと同様の構文を有しているlinariaを確認してみた。


小規模なアプリケーションの場合は良さそうだが、以下の点で linaria は見送った。

  • 動的なスタイルを多用した場合に CSS Custom Properties の出力が多くなり、HTML が肥大化してしまう

  • 動的なスタイルの値がundefinedな場合に不要なプロパティが残る

    const Heading = styled.h1`
      background-color: ${({ bg }) => bg};
    `;
    
    const Example = () => {
      return (
        <>
          <Heading bg="red">Red Heading</Heading>
          <Heading>Heading</Heading>
        </>
      );
    };
    

    <h1 class="t1vg9j5w" style="--t1vg9j5w-0:red;">Red Heading</h1>
    <h1 class="t1vg9j5w" style="--t1vg9j5w-0:undefined;">Heading</h1>
    
    <style>
      .t1vg9j5w {
        background-color: var(--t1vg9j5w-0);
      }
    </style>
    

    値がundefinedの場合でもプロパティが残る

  • 同じコンポーネントをネストすると再利用された CSS Custom Properties が上書きされていまい意図した動作をしない

    const Stack = styled.div`
      & > * + * {
        margin-top: ${({ spacing }) => spacing || "0"};
      }
    `;
    
    const Example = () => {
      return (
        <Stack spacing="1rem">
          <Stack></Stack>
          <Stack></Stack> {/* 👈 */}
        </Stack>
      );
    };
    

    ネストされた 2 番目のStackmargin-top1remとなることを期待するが、CSS Custom Properties の上書きによって0になってしまう

  • 採用事例・ドキュメントが少ない

styled-components vs Emotion

検討結果、候補は styled-components もしくは Emotion の 2 つになった。

  • 以前は後発の Emotion は利用できる API が多かった
    • 現状、styled-components の同様の機能も有している
      ライブラリ  タグ付きテンプレートオブジェクトスタイルグローバルスタイルネストセレクタTypeScript サポートServer Side Rendering
      styled-components
      Emotion
  • ベンチマーク結果を見ると Emotion の方が styled-components よりパフォーマンスは良い
    https://stitches.dev/docs/benchmarks

トレンドの比較

npm パッケージのダウンロード数だけで見ると emotion が styled-components よりも多くなっている(emotion はパッケージが分かれているため単純比較は難しい)。

npm trendsによる @emotion/styled と styled-components の比較
@emotion/styled-vs-styled-components

ちなみに GitHub のスター数は styled-components の方が多い。

next build の結果比較

Next.js 上で styled-components と Emotion のそれぞれを利用した場合のビルド結果を比較した。

ビルド後のファイルサイズを比較すると Emotion の方がファイルサイズは小さいようだった。

styled-components

対象パッケージは以下の通り。

next buildの実行結果は以下の通り。

Page                                       Size     First Load JS
┌ ○ /                                      3.27 kB        90.6 kB
├   /_app                                  0 B            87.3 kB
├ ○ /404                                   2.67 kB          90 kB
└ ○ /other                                 1.06 kB        88.4 kB
+ First Load JS shared by all              87.3 kB
  ├ chunks/framework-5f4595e5518b5600.js   42 kB
  ├ chunks/main-13980ce3a4ae3dcf.js        29 kB
  ├ chunks/pages/_app-5392701c868a5bd6.js  14.7 kB
  └ chunks/webpack-7b5c3cf34b918637.js     1.58 kB

https://github.com/hiro0218/css-in-js-analysis/tree/styled-components

Emotion

対象パッケージは以下の通り。

next buildの実行結果は以下の通り。

Page                                       Size     First Load JS
┌ ○ /                                      3.21 kB        84.1 kB
├   /_app                                  0 B            80.8 kB
├ ○ /404                                   2.67 kB        83.5 kB
└ ○ /other                                 1.06 kB        81.9 kB
+ First Load JS shared by all              80.8 kB
  ├ chunks/framework-5f4595e5518b5600.js   42 kB
  ├ chunks/main-13980ce3a4ae3dcf.js        29 kB
  ├ chunks/pages/_app-0710a7201ba41fb0.js  8.07 kB
  └ chunks/webpack-3bd2f374eaae16d4.js     1.71 kB

https://github.com/hiro0218/css-in-js-analysis/tree/emotion

まとめ

  • パフォーマンス観点だけでみれば Zero-runtime は魅力的だったが、要件を満たせなかった
  • styled-components や Emotion のようなユーザー数が多いライブラリを選択する方がナレッジを拾っていきやすいので良さそう
    好みの問題もあるが、パフォーマンス観点で言えば Emotion が良さそうという結論に至った