UnoCSSをTailwindユーザが触ってみた

この記事は [ここのえ Advent Calendar 2023](https://qiita.com/advent-calendar/2023/99no_exit) Day 3の記事です。

# Intro

ここ暫くはTailwind CSSをメインに使用しているのですが、先日公開された[State of CSS 2023](https://2023.stateofcss.com/en-US/css-frameworks/)で [**UnoCSS**](https://unocss.dev/) が紹介されていました。Tailwindとの親和性が高いので、触ってみることにしました。

# What's UnoCSS?

軽く調べてみた感じ、TailwindCSSからインスピレーションを得た `WindiCSS` を開発していたメンバーの1人が `UnoCSS` を作り始めた、という経緯のようです。公式Docによると、WindiCSSの「[**精神的後継**](https://unocss.dev/guide/why)」という立場だそうです。

`UnoCSS`の特徴として、**Atomic CSS Engine** であることが挙げられます。柔軟性・拡張性に優れ、必要な機能のみを導入することができます。

## UnoCSSの簡単な仕組み

少し掘り下げると、UnoCSSのCoreはあくまでベースのシステムだけ提供し、**Preset** がCSSユーティリティを提供する、という関係性です。

例えばTailwindのように `margin` を調整するために `m-1` のようなユーティリティを作るとします。UnoCSSではどうすれば良いでしょうか？

これは `uno.config.ts` 内にルールを定義しておくことで、それに対応したユーティリティが使えるようになります。

```ts
// uno.config.ts
import { defineConfig } from 'unocss'

export default defineConfig({
  rules: [
    ['m-1', { margin: '1px' }]
  ],
})
```

これで、`<div class="m-1">` のようにクラスを当てれば適用されるようになります。
ですが、これでは`m-1`の時にしか対応できないので、任意のmarginサイズに対応できるようにしたいので、正規表現を使って以下のように書き換えます。

```ts
// uno.config.ts
export default defineConfig({
  rules: [
+    [/^m-([\.\d]+)$/, ([_, num]) => ({ margin: `${num}px` })],
  ],
})
```

これによって、`m-100` や `m-3.14` といった自由な値のユーティリティを貼れるようになりました。

ただこれをプロジェクト毎に用意するのは面倒なので、**Preset** という仕組みがあります。

公式やコミュニティが作ったPresetが用意されています。自分が必要な機能が入っているPresetを随時導入していくことで、便利にUnoCSSを使うことができます。
  → [公式のPresetリスト](https://unocss.dev/presets/)

:::note info
Atomic CSSについての考え方は、UnoCSS作者の Anthony Fu 氏による [**Reimagine Atomic CSS**](https://antfu.me/posts/reimagine-atomic-css) という記事が非常に分かりやすいので、オススメです。
:::

# 導入

`npm`や`yarn`で導入します。

```bash
yarn add -D unocss
```

これだけではベースしか導入されていないので、好きなプリセットを入れます。例として基本の `@unocss/preset-uno` を導入します。

```bash
yarn add -D @unocss/preset-uno
```

導入したら、先述した `uno.config.ts` を作ってプリセットを読み込みます。

```ts
// uno.config.ts
import { defineConfig } from 'unocss'
import presetUno from '@unocss/preset-uno'

export default defineConfig({
  presets: [
    presetUno(),
  ],
})
```

数多くの環境に対応しており、[Vite](https://unocss.dev/integrations/vite) や [ESLint](https://unocss.dev/integrations/eslint) なども対応しています。

# Preset

公式Presetに絞って紹介します。

## @unocss/preset-uno, @unocss/preset-wind

https://unocss.dev/presets/wind

`preset-wind` は Tailwind, Windi互換のプリセットです。
Tailwindなどで使っていたユーティリティがそのまま使用できます。
設定されているルールも「Tailwindのドキュメントを見てね！」という潔さ。

`preset-wind`ですが、最低限のルールだけ定義した `preset-mini` を継承しています。

また `preset-uno` は同梱されているデフォルトのプリセットですが、こちらは `preset-wind` と同一の内容になっているようです[^1]。

[^1]: https://unocss.dev/presets/uno

## @unocsss/preset-attributify

https://unocss.dev/presets/attributify

クラスで当てるだけでなく、直接ユーティリティを指定できるようになります。

```html
<p
  bg="red"
  text="lg white"
  p="x-10 y-5"
  border="~ rounded"
>
  aaaa
</p>
```

## @unocss/preset-icons

https://unocss.dev/presets/icons

アイコンが表示できるようになります。 
[Iconify]()をデータソースとしており、使うアイコンのパッケージを導入しておく必要があります。

例：Material Design Iconsを使う場合
```bash
yarn add -D @unocss/preset-icons @iconify-json/mdi
```

```html
<div class="i-mdi-account-box text-blue-500" />
```

# @unocss/preset-web-fonts

https://unocss.dev/presets/web-fonts

`Google Fonts` や `FontShare` のフォントを容易に読み込めます。
`uno.config.ts`の設定が必要です。

```ts
export default defineConfig({
  presets: [
    presetWebFonts({
      provider: 'google',
      fonts: {
        lobster: 'Lobster',
      }
    })
  ],
})
```


```html
<p class="font-lobster text-lg">test</p>
```

## @unocss/preset-tagify

https://unocss.dev/presets/tagify

`flex` などがタグとして使えるようになります。
ちょっとインパクトが大きいので、使う場合はチームで相談。もちろんPresetごとに導入・導入見送りができるので、導入しないのも一つの選択です。

```html
<flex>
  <p>This is a flex box</p>
  <i-mdi-account-box />
</flex>
```

# Transformers

こちらは`Preset`と異なり、新しい構文をサポートするための機能です。

## Directives transformer

https://unocss.dev/transformers/directives

`@apply`や`@screen`といったディレクティブをサポートします。

```sass
.test
  @apply mt-4 border text-lg

@screen sm
  .test
    @apply text-2xl
```

## Variant group transformer

https://unocss.dev/transformers/variant-group

グループにまとめて書くことができます。
元々Windi CSSにあった機能のようですが、かなり便利そうです。

```html
<div class="font-(bold gray) bg-blue hover:(bg-red font-medium)">test</div>
```

# UnoCSS Inspector

https://unocss.dev/tools/inspector

Vite開発サーバを使用している時、`localhost:5173/__unocss` にアクセスすると、UnoCSSのインスペクタを確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617021/447337ee-aefc-0c66-0d30-0e4f5779ee6e.png)

# まとめ

UnoCSSを触ってみた感じとしては、Tailwindのユーティリティがそのまま使えることもあり、抵抗なく使う事が出来ました。

Tailwindの既存サイトについても、`tailwind.config.js`などの自前テーマやカラースキームさえ移行すれば、そのままコードが転用できるので移行コストもかなり低そうです。

大きな違いとしては、公式のプリセットで定義されたルールが小数点以下も対応しているため、`m-55`や`m-12.3` のようなTailwindで存在しなかった数値も`[]`を使わずに指定でき、ストレスフリーで良いと思いました。

今回動作スピードについての検証は行いませんでしたが、ホットリロードの反映速度もかなり高速に感じました。UnoCSSの公式ページでは、

> It's 5x faster than Windi CSS or Tailwind JIT.

と謳われているので、実際早そうです。

[**Intaractive Docs**](https://unocss.dev/interactive/) や、 [**PlayGround**](https://unocss.dev/play/) も使いやすいので、開発も進めやすいです。また `v1.0` にはなっていませんが、現時点でかなり良好なので今後のプロダクトで採用したいな～という気持ちです。

# 参考資料

- [UnoCSS 公式ドキュメント](https://unocss.dev/)