# CSS から React コンポーネントを生成する MistCSS

昨今のフロントエンド開発では、CSS の手法が多様化しています。特に React での開発では以下のような手法があげられます。

- グローバル CSS（エントリーポイントで 1 つの CSS ファイルを読み込む）
- CSS Modules
- CSS in JS
- Tailwind CSS

CSS の手法に新たな選択肢が加わりました。それが [MistCSS](https://typicode.github.io/mistcss/) です。MistCSS は CSS in JS になぞらえた JS from CSS というコンセプトで、CSS から React コンポーネントを生成するツールです。

MistCSS のメリットとして、以下のような点が挙げられます。

- ピュアな CSS を記述できるので、学習コストが低い
- 生成されたコンポーネントは自動で型安全になる
- コンポーネントがシンプルでステートレスな設計になる
- ゼロランタイムで動作するので、パフォーマンスに影響がない

## MistCSS を使ってみる

実際に MistCSS を使ってみましょう。以下のコマンドで MistCSS をインストールします。

```bash
npm install --save-dev mistcss
```

JS From CSS というコンセプトのとおり、まずは CSS を記述します。MistCSS の自動生成の対象とする CSS ファイルはいくつかのルールがあります。

- CSS ファイルの拡張子は `.mist.css` であること
- コンポーネント名を [@scope](https://developer.mozilla.org/ja/docs/Web/CSS/@scope) で指定する。このクラス名はプロジェクトで一意である必要がある
- [:scope](https://developer.mozilla.org/ja/docs/Web/CSS/:scope) 擬似クラスでコンポーネントのルート要素を指定する
- Props で受け取る値を `data-` で指定する

例として、以下の Props を受け取る `<Button>` コンポーネントを作成してみましょう。

- `variant`：`primary` または `secondary`
- `disabled`：`true` または `false`

```css:src/components/button.mist.css
/*
 * @scope() の値に .button を指定すると Button コンポーネントが生成される
 */
@scope (.button) {
  /** <button> 要素がコンポーネントのルート要素になる */
  button:scope {
    /* 共通のスタイル */
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
    font-size: 16px;
    cursor: pointer;

    /**
     * <Button variant="primary"> の場合
     */
    &[data-variant='primary'] {
      background-color: #007bff;
      color: #fff;
    }

    /**
     * <Button variant="secondary"> の場合
     */
    &[data-variant='secondary'] {
      background-color: #6c757d;
      color: #fff;
    }

    /**
     * <Button disabled> の場合
     */
    &[data-disabled] {
      opacity: 0.5;
      cursor: not-allowed;
    }
  }
}
```

!> 2024 年 3 月現在、属性の値は必ずシングルクォート（`'`）で囲む必要があるようです。ダブルクォートで囲むと Props の型が正しく生成されませんでした。

CSS の記述が完了したら、以下のコマンドで React コンポーネントを生成します。

```bash
npx mistcss ./src
```

コマンドの実行に成功すると、`src/components/button.mist.css.tsx` が生成されます。

```tsx:src/components/Button.tsx
// Generated by MistCSS, do not modify
import './button.mist.css'

type ButtonProps = {
  children?: React.ReactNode
  variant?: 'primary' | 'secondary'
  disabled?: boolean
} & JSX.IntrinsicElements['button']

export function Button({ children, variant, disabled, ...props }: ButtonProps) {
  return (
    <button {...props} className="Button" data-variant={variant} data-disabled={disabled}>
      {children}
    </button>
  )
}
```

姿勢されたコンポーネントは以下のように使用できます。

```tsx:src/App.tsx
import { Button } from './components/Button'

export function App() {
  return (
    <div>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button disabled>Disabled</Button>
    </div>
  )
}
```

## 1 つの CSS ファイルに複数のコンポーネントを記述する

複数の `@scope` を記述することで、1 つの CSS ファイルから複数のコンポーネントを生成することができます。これは `<DialogHeader>`、`<DialogBody>`、`<DialogFooter>` などのように、複数のコンポーネントで 1 つの UI を構成する場合に便利です。

```css:src/components/dialog.mist.css
@scope (.dialog) { /** */ }
@scope (.dialog-header) { /** */ }
@scope (.dialog-body) { /** */ }
@scope (.dialog-footer) { /** */ }
```

```ts
import {
  Dialog,
  DialogHeader,
  DialogBody,
  DialogFooter,
} from "./components/Dialog";
```

## CSS による論理演算

```css
/* foo=bar かつ baz=qux の場合 */
&[data-foo="bar"]&[data-baz="qux"] {
  /**  */
}
```

```css
/* foo=bar または baz=qux の場合 */
&[data-foo="bar"],
&[data-baz="qux"] {
  /** */
}
```

## まとめ

- MistCSS は CSS から React コンポーネントを生成するツール
- ピュアな CSS を記述できるので、学習コストが低い、パフォーマンスに影響がないといったメリットがある
- CSS を以下のルールに従って記述し、コマンドを実行することでコンポーネントが生成される
  - CSS ファイルの拡張子は `.mist.css`
  - コンポーネント名を `@scope` で指定する
  - `:scope` 擬似クラスでコンポーネントのルート要素を指定する
  - Props で受け取る値を `data-` で指定する

## 参考

- [MistCSS](https://typicode.github.io/mistcss/)
- [typicode/mistcss: Write atomic React components using only CSS! (JS-from-CSS™) 🌬️](https://github.com/typicode/mistcss)
  