Vite + StyleX を試してみる

## この記事の概要

Meta 社製の CSS in JS ライブラリである StyleX が登場しました。

https://stylexjs.com/

[2021 年には言及されていた](https://www.infoq.com/news/2021/10/facebook-css-js-stylex/)ようですが、[2023 年 12 月 5 日（現地時間）に一般公開された](https://stylexjs.com/blog/introducing-stylex/)みたいです。

普段自分は Vite を使うことが多いので、Vite + StyleX の組み合わせを試してみます。

## Vite の準備

まずは作成します。

```sh
bun create-vite 
```

React, TypeScript, SWC を選択しました。

## StyleX の準備

まずは[公式ドキュメント](https://stylexjs.com/docs/learn/installation/)にある通り進めます。

```sh
bun i @stylexjs/stylex
```

続いて、Vite は Rollup を使っているので `@stylexjs/rollup-plugin` を入れて動かそうとしましたが、駄目でした。

[Issue](https://github.com/facebook/stylex/issues/47) で議論されていて、[Pull Request](https://github.com/facebook/stylex/pull/66) も作成中なようで、近いうちに公式からサポートされそうではありますが、2023 年 12 月 11 日現在は上手く動きません。

しかし調べたら個人の方がプラグインを作ってくださっていました。
ありがたく使わせていただきます。

https://github.com/HorusGoul/vite-plugin-stylex

```sh
bun i -d vite-plugin-stylex
```

```diff_typescript:vite.config.ts
  import { defineConfig } from "vite";
  import react from "@vitejs/plugin-react-swc";
+ import styleX from "vite-plugin-stylex";

  export default defineConfig({
-   plugins: [react()],
+   plugins: [react(), styleX()],
  });
```

これで準備は完了です。
以下は依存関係とバージョンの一覧です。

```jsonc:package.json
{
  // 省略
  "dependencies": {
    "@stylexjs/stylex": "^0.3.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.37",
    "@types/react-dom": "^18.2.15",
    "@typescript-eslint/eslint-plugin": "^6.10.0",
    "@typescript-eslint/parser": "^6.10.0",
    "@vitejs/plugin-react-swc": "^3.5.0",
    "eslint": "^8.53.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.4",
    "typescript": "^5.2.2",
    "vite": "^5.0.0",
    "vite-plugin-stylex": "^0.1.0"
  }
}
```

## 基本の書き方

`stylex` を import した上で

```tsx
import * as stylex from "@stylexjs/stylex";
```

`stylex.create()` でスタイルを生成して

```tsx
const styles = stylex.create({
  foo: {
    color: 'red',
    fontSize: '2em'
  },
  bar: {
    backgroundColor: 'blue',
  },
});
```

`stylex.props()` で使用します。

```tsx
{/* 個別に適用 */}
<div {...styles.props(styles.foo)}>foo</div>
<div {...styles.props(styles.bar)}>bar</div>

{/* あわせて適用 */}
<div {...styles.props(styles.foo, styles.bar)}>foo bar</div>
```

## 通常のスタイルを書く

`create-vite` で生成された `App.tsx` にスタイルを当ててみます。

```diff_tsx:App.tsx
  import { useState } from "react";
  import reactLogo from "./assets/react.svg";
  import viteLogo from "/vite.svg";
  import "./App.css";
+ import * as stylex from "@stylexjs/stylex";

+ const styles = stylex.create({
+   title: {
+     color: "#55c500",
+   },
+ });

  function App() {
    const [count, setCount] = useState(0);

    return (
      <>
        {/* 省略 */}
        <h1 {...stylex.props(styles.title)}>Vite + React</h1>
        {/* 省略 */}
      </>
    );
  }

  export default App;
```

| before | after |
| --- | --- |
| ![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/9b73e06d-bd81-3c53-9933-85a5a9b58814.png) | ![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/e0f3d6e7-51c4-42b9-27b7-b89d8b42dbc5.png) |

ただ色を変えただけですが、上手く動いています。

以下は出力されたコードです。

```html:HTML
<h1 class="App__styles.title x1g81dyc">Vite + React</h1>
```

```css:CSS
.x1g81dyc {
    color: rgb(85, 197, 0);
}
```

少しスタイルを追加してみます。

```diff_tsx:App.tsx
  const styles = stylex.create({
    title: {
      color: "#55c500",
+     fontSize: "5rem",
+     marginBlock: 0,
    },
  });
```

出力されるコードも変わりました。
1 つのプロパティにつき 1 つのクラスが追加されるようです。

```html:HTML
<h1 class="App__styles.title x1g81dyc xn7rfii x10im51j">Vite + React</h1>
```

```css:CSS
.x10im51j {
    margin-block: 0px;
}

.xn7rfii {
    font-size: 5rem;
}

.x1g81dyc {
    color: rgb(85, 197, 0);
}
```

先ほどリンクを貼ったブログ内で `atomic class names` と書かれていた作りです。
Tailwind CSS のアプローチと同じような感じです。

## state にあわせたスタイルを書く

特別紹介するほどのものでもないかもしれませんが、state にあわせてスタイルを変更するのも問題ないようです。

```diff_tsx:App.tsx
  const styles = stylex.create({
    title: {
      color: "#55c500",
      fontSize: "5rem",
      marginBlock: 0,
    },
+   button: {
+     backgroundColor: "#ddd",
+   },
+   active: {
+     backgroundColor: "#212121",
+     color: "white",
+   }
  });

{/* 中略 */}

  <button
    onClick={() => setCount((count) => count + 1)}
+   {...stylex.props(count > 0 ? styles.active : styles.button)}
  >
    count is {count}
  </button>
```

想定通り変化しています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/19e6ffc2-9e38-ee3f-7e29-c2901630e0a7.gif)

## 最後に

まだバージョンは 1 にも満たないので本番適用するのは怖いですが、Meta が出しているなら React との相性は良いはずでは？とも思えます。

期待しつつ引き続き見ておきます。