🍦

vanilla-extractを使ってみた感想

2022/02/27に公開

個人的にvanilla-extractを試していたのでその感想などを紹介できたらと思います。

https://vanilla-extract.style/

要点

  • CSS modulesっぽくかけて型があるのがとても良い
  • 採用したらメリットが大きそうなケース
    • オブジェクト形式でも違和感なくかける人
    • CSSに型が欲しい人
    • CSSでNestを制限したいなど秩序をもたらしたい人

CSS modulesっぽくかけるのがとても良い

個人的に従来のピュアなCSSやCSS modulesのように別ファイルでCSSを管理するのが好みなのでこの書き方ができるのはメリットでした。
特にCSS modulesのようにファイルを管理できるのが良いです。

header.css.ts
import { style } from '@vanilla-extract/css';

export const headerStyle = {
  wrapper: style({
    padding: '16px',
    display: 'flex',
    alignItems: 'center',
  }),
  title: style({
    padding: '0 16px',
  }),
  link: style({
    textDecoration: 'none',
    ':hover': {
      opacity: 0.5,
    },
  }),
};

上記のように書くことでCSS modulesのようにクラス名を参照しながらスタイルを当てていくことができるのでCSS modulesを使用したことがあるなら違和感なく書くことができます。
CSS modulesとの違いとしてオブジェクト形式ということがあげられます。これによってCSSのプロパティが保管されるというメリットを受けることができる一方でこの書き方に慣れないという方ももしかしたらいるかもしれません。

実際にスタイルを当てる側のコンポーネントは以下のようになります。
CSS modulesのようにクラス名を当てていけるのが良いですね。クラス名も自動で補完されます。

header.tsx
import Link from 'next/link';
import { headerStyle } from './header.css';

export const Header = () => {
  return (
    <header className={headerStyle.wrapper}>
      <h1 className={headerStyle.title}>Title</h1>
      <Link href="/link">
        <a href="/link" className={headerStyle.link}>
          <p className={headerStyle.link}>Link</p>
        </a>
      </Link>
    </header>
  );
};

採用したらメリットが大きそうなケース

vanilla-extractはCSS modulesをベースにTypeScriptのようにプロパティに型を与えることで、大きなメリットをもたらします。
自分は個人レベル(Jamstack形式でのWEBサイト制作)で使用したのみですが、このような場合にvanilla-extractが良さそうだなあというのをまとめておこうと思います。

オブジェクト形式でも違和感なくかける人

上述の通り、オブジェクト形式での記法であるため従来のCSSとは記法が異なります。オブジェクト形式の方が好きだという人はあまりいないイメージ(自分調べ)があるので、拒否感が強いチームだとメリットがあまりないかもしれません。

CSSに型が欲しい人

vanilla-extractは公式サイトにて、

Use TypeScript as your preprocessor. Write type‑safe, locally scoped classes, variables and themes, then generate static CSS files at build time.

とあるようにTypeScriptでビルド時に静的なCSSを生成するため、誤ったプロパティやクラス名を指定するとコンパイルエラーを出してくれます。
開発時やビルド時にタイプチェックをしてくれるので、タイポなどのチェックがされるので開発効率があがります。

しかしCSSの難しいところはコンパイルが通ったとしても、詳細度などで必ずしもスタイルが適用されないことです。ですので、CSSのチェックをビルド時にすべきメリットを天秤にかける必要もあるでしょう。

VSCodeなどのエディタでは、誤ったCSSプロパティ名を指定するとハイライトがされないので気づくことができたり、sylelintなどのツールを使ってチェックすることも可能です。

https://stylelint.io/

一方でクラス名の自動補完が得られるというのはvanilla-extractの大きなメリットです。ファイルを2つ広げながらコーディングをするのは...という人は検討してみても良いのではないでしょうか?

CSSでNestを制限したいなど強制力をもたらしたい人

ここまでvanilla-extractの特徴やメリットなどを述べされていただきましたが、個人的にここがvanilla-extractを採用すべきか否かの大きなポイントかと思います。

vanilla-extractではSCSSやPostCSSのようなCSSをNestする記法がサポートされていません。
SCSSでいうところの以下のような記法です。

.className {
  display: flex;
  align-items: center;

  > * {
    margin-right: 8px;
  }
}

vanilla-extractではSelectorを使用します。
公式サイトのソースコードを一部引用します。
https://vanilla-extract.style/documentation/styling-api/

import { style } from '@vanilla-extract/css';
import { vars } from './vars.css.ts';

export const className = style({
  display: 'flex',
  ':hover': {
    color: 'red'
  },
  selectors: {
    '&:nth-child(2n)': {
      background: '#fafafa'
    }
  },
});

上記引用ページには以下のような註釈があります。

💡 To improve maintainability, each style block can only target a single element. To enforce this, all selectors must target the “&” character which is a reference to the current element.For example, '&:hover:not(:active)' and [${parentClass} &] are considered valid, while '& a[href]' and [& ${childClass}] are not.
If you want to target another scoped class then it should be defined within the style block of that class instead.
For example, [& ${childClass}] is invalid since it doesn’t target “&”, so it should instead be defined in the style block for childClass.
If you want to globally target child nodes within the current element (e.g. '& a[href]'), you should use globalStyle instead.

Selectorを使用してスタイルを適用するには以下のように親クラスを適用するか、

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

export const parentClass = style({});

export const childClass = style({
  selectors: {
    [`${parentClass}:focus &`]: {
      background: '#fafafa'
    }
  }
});

擬似要素を用いる必要があります。 & a[href] のようにスタイルを当てたい場合は、globalStyleを使用します。

https://vanilla-extract.style/documentation/styling-api/#globalstyle

このように厳密にスタイルを適用することによって保守性を高め、CSS設計をすることを強要させたい場合vanilla-extractは有効な選択肢になるかと思います。

まとめ

vanilla-extractの技術選定ではTypeScriptによるCSSのタイプチェックが重視されるかと思いますが、個人的には最後のCSS設計に関わる部分も重視すべきポイントのように思います。

vanilla-extractはプロパティやクラス名の補完を行いコーティングのスピードを上げるものではありません。むしろオブジェクト記法などによりスピードは落ちます。
ゆえにJamstackを用いたWEB制作の分野での使用は適していないように思えます(それはそう...と思われるかもしれませんが)。

CSS設計、コンポーネント設計に強制力を持たせるものとしてvanilla-extractを見ることで、より良い選定ができるのではないかと思いこのような文章を書かせていただきました。

重ねてですが、この文章はvanilla-extractを本番運用したわけではなく個人的な使用の感想を述べたものになります。
ぜひ本番運用の事例も出てきたら読んでみたいなと思います。

Discussion