Meta社の新CSS-in-JSソリューション StyleX を使ってみた

つい2週間前12/1にMeta社よりStyleXという新しいCSS-in-JSライブラリが公開されました。

https://github.com/facebook/stylex

> StyleX is a JavaScript library for defining styles for optimized user interfaces.

StyleXの特徴としてはここの公式ドキュメントに記載されています。要するに早くて最適化されていてtype safeと書かれています。

https://stylexjs.com/docs/learn/thinking-in-stylex/

まだ新しいライブラリのため、公式ドキュメントがうまく動作しなかったため記事にしました。しかし、今後仕様は変わる可能性があります。

# 使ってみた

## 本番環境で使用する

今回はNext.jsに組み込んでみます。基本的には公式ドキュメント通りに実行します。

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

ちなみに公式のサンプルもあります。

https://github.com/facebook/stylex/tree/main/apps/nextjs-example

早速既存のプロジェクトに導入だ！と言いたところですが、動きません。動かすためにいくつか手伝ってあげましょう。

### インストール

この5つを入れてください。

```bash
yarn add @stylexjs/stylex
yarn add -D @stylexjs/nextjs-plugin
yarn add -D @stylexjs/babel-plugin
yarn add -D @stylexjs/eslint-plugin # eslintを使っている場合
yarn add -D @stylexjs/dev-runtime # 開発環境でも使用したい場合
```

### TypeScriptのバージョンを上げる

どこから動くかは調べていませんが、5以上なら動きます。

### next.config.jsの修正

公式ドキュメントと違いますが、公式サンプルと似たコードになっています。

```js:next.config.js
/** @type {import('next').NextConfig} */
const stylexPlugin = require("@stylexjs/nextjs-plugin");
const babelrc = require("./.babelrc.js");
const plugins = babelrc.plugins;
const [_name, options] = plugins.find(
  (plugin) => Array.isArray(plugin) && plugin[0] === "@stylexjs/babel-plugin",
);
const rootDir = options.unstable_moduleResolution.rootDir ?? __dirname;

const stylexConfig = stylexPlugin({
  rootDir,
  useCSSLayers: true,
})({});

module.exports = {
  ...stylexConfig,
  output: "export", // Static Site Generateする場合のみ
};
```

### babelの設定

公式ドキュメントに書かれているbabel.config.jsは不要でした。代わりに.babelrc.jsは必要です。

これは基本的にドキュメントの通りで問題ありませんが、後述の「開発環境で使用する」を実施する場合はコメントの通り 1 行だけ変えてください。

```js:.babelrc.js
module.exports = {
  presets: ["next/babel"],
  plugins: [
    [
      "@stylexjs/babel-plugin",
      {
        dev: process.env.NODE_ENV === "development",
        runtimeInjection: true, // ここだけ変えた
        genConditionalClasses: true,
        treeshakeCompensation: true,
        unstable_moduleResolution: {
          type: "commonJS",
          rootDir: __dirname,
        },
      },
    ],
  ],
};

```

### .eslintrc.jsonの設定

公式ドキュメントの通りで大丈夫です。

```js:.eslintrc.js
module.exports = {
  plugins: ["@stylexjs"],
  rules: {
    "@stylexjs/valid-styles": ["error", {...options}],
  },
};
```

## 開発環境で使用する

先ほどまでのコードは環境変数に`NODE_ENV=production`が入った場合のみスタイルを適用するようになっています。そのため、`next dev`で開発している際にもスタイリングしてあげましょう。

ちなみにこれは公式ドキュメント通りでは動かないので注意です。

```tsx:_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import inject from "@stylexjs/dev-runtime";

if (process.env.NODE_ENV !== "production") {
  inject({
    classNamePrefix: "x",
    dev: true,
    test: false,
    useRemForFontSize: false,
    runtimeInjection: false,
    styleResolution: "application-order",
  });
}

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

export default MyApp;
```

## スタイルを適用する

こんな感じでかけます。

```tsx:index.ts
import { NextPage } from "next";
import stylex from "@stylexjs/stylex";

export const s = stylex.create({
  redOrBlueText: {
    color: {
      default: 'red',
      ":hover": 'blue'
    }
  },
  backYellow: {
    backgroundColor: 'yellow'
  }
})
const Home: NextPage = () => {
  return <div {...stylex.props(s.redOrBlueText, s.backYellow)}>ABC</div>
}
```

通常時
![スクリーンショット 2023-12-16 17.40.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343837/02b8e3b3-8b9b-97f4-ee30-23791c062fd3.png)

カーソルをホバーしたとき
![スクリーンショット 2023-12-16 17.41.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343837/edb303aa-d2b3-fbcd-b1d3-b8894cd646b8.png)

🎉🎉🎉

### おわり

多分数日後にはこの記事が動かなくなっていますが、その時はすみません。