StyleX：FacebookやInstagramで利用されているCSS-in-JS

約1週間前の[2023/12/5](https://stylexjs.com/blog/introducing-stylex/)にMeta社が開発するCSS-in-JSライブラリ、[StyleX](https://stylexjs.com/)が[オープンソースとして公開](https://github.com/facebook/stylex)されたので触ってみました。

# StyleX
FacebookやInstagram、Threadsなど[Meta社のプロダクトで利用](https://stylexjs.com/blog/introducing-stylex/#how-does-meta-use-stylex)されており、この度晴れてオープンソース化したCSS-in-JSです。

[React Finland 2021](https://www.youtube.com/watch?v=ur-sGzUWId4)の講演で既に紹介されており、この時点では[2021年にオープンソース化](https://youtu.be/ur-sGzUWId4?t=845)の見込みとなっていました。

# 特徴

https://stylexjs.com/docs/learn/

特徴は上記でまとまっていますが、改めて大事なところを抜粋し意訳します。

> ### Fast
>
>    - No runtime style injection.
>    - All styles are bundled in a static CSS file at compile-time.
>    - Optimized runtime for merging class names.

速いです。いわゆるゼロランタイムで、コンパイル時に静的なCSSファイルを生成しバンドルします。

> ### Scalable
>
>    - Minimize CSS output with atomic CSS.
>    - The CSS size plateaus even as the number of components grows.
>    - Styles remain readable and maintainable within growing codebases.

スケーラブルです。最適化されたアトミックCSS[^atomic]を生成するので、コードベースが大きくなってコンポーネントが増えても、CSSファイルのサイズは線形に増大せずある程度で落ち着きます。


> ### Type-Safe
>
>    - Type-safe APIs.
>    - Type-safe styles.
>    - Type-safe themes.

タイプセーフです。

# React Server Components との相性
その仕組みゆえ、React Server Components でも動作します。

https://github.com/facebook/stylex/issues/123#issuecomment-1849088489

ドキュメントに明示的にその記載はありませんが、GitHubのissueでメンテナーの方も"StyleXはReact Server Componentsで動作する"と答えています。

> >Will all the StyleX features work without restrictions in all React components?
>
> Yes. All of StyleX will work with React, Next and React Server Components.

# 触ってみる
ここから実際に触ってみます。プロダクション向けには、

- [Rollup](https://github.com/facebook/stylex/tree/main/packages/rollup-plugin)
- [Webpack](https://github.com/facebook/stylex/tree/main/packages/webpack-plugin)
- [Next.js](https://github.com/facebook/stylex/tree/main/packages/nextjs-plugin)

の3つが現時点ではサポートされています。今回はNext.jsで使ってみました。

:::note info

viteプラグインは、[issue](https://github.com/facebook/stylex/issues/47)で議論されています。

:::

## インストールと設定
packageのインストールし.babelrc.js、next.config.js を以下に従い設定します。

https://stylexjs.com/docs/learn/installation/

...が、create-next-appして、packageインストールして、上記の設定コピペしてもうまく動かなかったので、おとなしく公式が出している[example](https://github.com/facebook/stylex/tree/main/apps/nextjs-example)の.babelrc.js、next.config.jsを拝借しました。

## スタイリングの実装
スタイルやテーマの定義、利用方法は公式ドキュメントにまとまっています。

https://stylexjs.com/docs/learn/styling-ui/defining-styles/

ここでは、create-next-appで作成した画面をStyleXで実装するとどうなるか、CSS Modulesとの対比で記載してみます。

### CSS Moduleの場合
```css:page.module.css より抜粋
.main {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  padding: 6rem;
  min-height: 100vh;
}

.description {
  display: inherit;
  justify-content: inherit;
  align-items: inherit;
  font-size: 0.85rem;
  max-width: var(--max-width);
  width: 100%;
  z-index: 2;
  font-family: var(--font-mono);
}
```

```tsx:page.tsx より抜粋
export default function Home() {
  return (
    <main className="main">
      <div className="description">
          {/*(略)*/}
      </div>
    </main>
    )
}
```

### StyleXの場合

```tsx:tokens.stylex.ts より抜粋
// デザイントークン（CSS変数）の定義
export const tokens = stylex.defineVars({
    maxWidth: "1100px",
    borderRadius: "12px",
    //（略）
```

```tsx:page.tsx より抜粋
import * as stylex from '@stylexjs/stylex';
import { tokens } from "./tokens.stylex";

//スタイルの定義
const styles = stylex.create({
  main: {
    display: "flex",
    flexDirection: "column",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "6rem",
    minHeight: "100vh"
  },
  description: {
    display: "inherit",
    justifyContent: "inherit",
    alignItems: "inherit",
    fontSize: "0.85rem",
    maxWidth: tokens.maxWidth,
    width: "100%",
    zIndex: "2",
    fontFamily: tokens.fontMono,
  }
  
  //(略)

});

export default function Home() {
  return (
    {/*スタイルの利用*/}
    <main {...stylex.props(styles.main)}>
      <div {...stylex.props(styles.description)}>
          {/*(略)*/}
      </div>
    </main>
    )
}  
```
基本的には

 - [`stylex.create`](https://stylexjs.com/docs/api/javascript/create/)でスタイルを定義
 - [`stylex.defineVars`](https://stylexjs.com/docs/api/javascript/defineVars/)でCSS変数を定義
 - [`stylex.props`](https://stylexjs.com/docs/api/javascript/props/)でスタイルを適用

という形になります。



## 生成されたCSS
StyleXで実装した画面を開発者ツールで覗いてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3024644/27d58a6e-0509-2e22-63fc-504dbbeb4e4f.png)

以下のように、実行時にはタグに複数のクラスが付与されており、1クラスが1つの機能/役割を持っていることがわかります。これが特徴の一つ、アトミックCSS[^atomic]を生成する、ということでしょう。

```html
<main class="x78zum5 xdt5ytf x1qughib x6s0dn4 x1hm14om xg6iff7">
```

```css
.xg6iff7 {
    min-height: 100vh;
}

.x1qughib {
    justify-content: space-between;
}
```

## はまったところ
使い方は直感的で、触った範囲では概ね問題なく動きましたが、いくつかはまったポイントがありました。

- `.babelrc.js`、`next.config.js`がドキュメントの設定ではうまく動作しなかった
  - => 公式の[example](https://github.com/facebook/stylex/tree/main/apps/nextjs-example)の設定をコピーして利用
- `stylex.defineVars`で定義した変数をaliasを利用してインポートできない
  - => 変数のインポートではaliasを利用しない。こちらの[issue](https://github.com/facebook/stylex/issues/40)です。

まだStyleXは公開されたばかりですし、これから改善してくものと思われます。


# おわりに

React Server Componentsに対応している点や、FacebookやInstagramのようなプロダクトで実績がある点は魅力的です。

一方で、触ってみると上記のようにやや粗削りな印象もありました。これからユーザのフィードバックが集まり洗練されれば、プロダクションで利用するスタイリング手法の選択肢に入ってくるのかなと思いました。

今後の動きに注目、期待したいです。

[^atomic]:アトミックCSSという言葉は一般的なんでしょうか？私は「1クラスに最低限の（≒1つの）役割のみ持たせたCSSの設計手法」ととらえています。Tailwind のようなUtility Firstと同じようなイメージです。