CSS-in-JS記述方法の比較

技術選定のためにCSS-in-JSそれぞれの特徴を知りたいと思ったため簡単なコンポーネントで書き方の違いを確かめてみました。

開発環境は下記のとおりです。
- react: 18.2.0
- next: 13.3.1
- typescript: 5.0.4
- storybook: 7.3.2

:::note
それぞれの書き心地を確かめるための環境を作りましたので、学習されたい方は宜しければ参考にしてください！
TailwindCSSも記述できる環境となっています。
:::

https://github.com/hikari0709/react-website-library

利用するCSSは下記です。
class名はそれとなくつけたものなので細かい違和感はご了承ください。

```sass
.card {
  width: 288px;
  height: auto;
  border-radius: 4px;
  border: 1px solid #ccc;
  padding: 8px;
}

.imageFrame {
  width: 100%;
  height: auto;
  margin-bottom: 12px;
}

.body {
  width: 100%;
  height: auto;
  background-color: #fff;
  box-sizing: border-box;
}

.title {
  font-size: 18px;
  font-weight: bold;
  margin-bottom: 4px;
}

.detail {
  font-size: 14px;
  line-height: 1.5;
}

```

表示すると下記のような感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/4290870f-9981-2734-43f5-f35cdfda0fdb.png)

## CSS Modules
``` javascript
import { FC, PropsWithChildren } from 'react';
import Image from 'next/image';
import styles from './modulesCard.module.scss';

type Props = PropsWithChildren<{ title: string }>;

const Card: FC<Props> = ({ title, children }) => {
  return (
    <div className={styles.card}>
      <div className="imageFrame">
        <Image
          src="https://placehold.jp/640x360.png"
          alt=""
          width={640}
          height={360}
        />
      </div>
      <div className={styles.body}>
        <p className={styles.title}>{title}</p>
        <p className={styles.detail}>{children}</p>
      </div>
    </div>
  );
};

export default Card;
```


## styled-components
```javascript
/**
 * padding,radius,borderのサイズエレベーションを自由に調整できるようにする
 */
import { FC, PropsWithChildren } from 'react';
import styled from 'styled-components';
import Image from 'next/image';

type Props = PropsWithChildren<{ title: string }>;

const CardStyled = styled.div`
  width: 288px;
  height: auto;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 8px;
`;

const ImageFrame = styled.div`
  width: 100%;
  height: auto;
  margin-bottom: 12px;
`;

const Body = styled.div`
  width: 100%;
  height: auto;
  background: #fff;
  box-sizing: border-box;
`;

const Title = styled.p`
  font-size: 18px;
  font-weight: bold;
  margin-bottom: 4px;
`;

const Detail = styled.p`
  font-size: 14px;
  line-height: 1.5;
`;

const Card: FC<Props> = ({ title, children }) => {
  return (
    <CardStyled>
      <ImageFrame>
        <Image
          src="https://placehold.jp/640x360.png"
          alt=""
          width={640}
          height={360}
        />
      </ImageFrame>
      <Body>
        <Title>{title}</Title>
        <Detail>{children}</Detail>
      </Body>
    </CardStyled>
  );
};

export default Card;
```

## vanilla-extract

利用する場合は設定が必要なようなので下記のページを参考に環境設定をします。

https://vanilla-extract.style/documentation/integrations/next/

styleは下記のように定義してみました。

```javascript
import { style } from '@vanilla-extract/css';

export const card = style({
  width: '288px',
  height: 'auto',
  border: '1px solid #ccc',
  borderRadius: '4px',
  padding: '8px',
});

export const imageFrame = style({
  width: '100%',
  height: 'auto',
  marginBottom: '12px',
});

export const body = style({
  width: '100%',
  height: 'auto',
  backgroundColor: '#fff',
  boxSizing: 'border-box',
});

export const title = style({
  fontSize: '18px',
  fontWeight: 'bold',
  marginBottom: '4px',
});

export const detail = style({
  fontSize: '14px',
  lineHeight: '1.5',
});
```

コンポーネントの記述は下記のとおりです。
```javascript
import { FC, PropsWithChildren } from 'react';
import Image from 'next/image';
import * as styles from './vanillaExtractCard.css';

type Props = PropsWithChildren<{ title: string }>;

const Card: FC<Props> = ({ title, children }) => {
  return (
    <div className={styles.card}>
      <div className={styles.imageFrame}>
        <Image
          src="https://placehold.jp/640x360.png"
          alt=""
          width={640}
          height={360}
        />
      </div>
      <div className={styles.body}>
        <p className={styles.title}>{title}</p>
        <p className={styles.detail}>{children}</p>
      </div>
    </div>
  );
};

export default Card;
```

:::note warn
今回はCSSを可読性を担保したかったので別ファイルに分けていますが、プロジェクトの状況によっても異なると思うので同一ファイルに記述するかどうかは判断の分かれるところかと思います。
:::

### まとめ
それぞれのメリット・デメリットはもちろんありますが、個人的にはTypeScriptの恩恵が享受できる「vanilla-extract」が良かったかな…という所感です。
これまでCSSを記述する際にはJSのような強力なLintはなかったと思っていますが、vanilla-extractはそれを実現してくれます。

これまでのCSSと比べると窮屈に感じる人もいるかもしれませんが、大きなプロジェクトになってくるとコーディングルールだけでは年月と共にCSSの変更難易度は高まるばかりなのでTypeScriptの恩恵を受けていくというのも選択の一つではないかと思っています。

学習用のブランチではありますが、それぞれのCSSの書き心地を検証してみたい！
という方は学習用のブランチではありますが、
- CSS Modules
- styled-components
- vanilla-extract
- TailwindCSS
を記述できる環境を作ってありますので、宜しければご利用ください！

https://github.com/hikari0709/react-website-library