CSS

linariaからNext.jsのCSS Modulesに切り替えた

本ブログのスタイルはlinariaを利用していましたが、Next.jsのCSS Modulesに切り替えました。その時の備忘録です。

理由

このブログを作成した頃はCSS-in-JSが潮流、特にZero Runtimeが流行っていたためlinariaを使ってみたく採用してみました。vanilla-extractもそうですが、設定が少し面倒なだけでかなり書き心地は良いと思ってます。

linaria、正確にはWyW-in-JSはTransformerとしてbabelに依存しています[1]
一方でこのブログはNext.jsのSSGを利用したものとなっています。Next.jsはv12よりSWCによるコンパイルが有効になっています。linariaを利用するためにはbabelが必要となり、SWCの利用ができないです。
WyW-in-JSに切り替わったlinaria v6へのアップデートするタイミングでそのあたりのコンセプトの理解も正直ドキュメントが充実しておらず難しく、切り替えを決断しました(決断したのは半年ぐらい前でしたが、面倒だったので放置してました)。

CSS Modulesに切り替えた手順

愚直にやるだけです。が、自分が理解していないだけではあったんですが、ハマりどころがありました。

linariaでは下記のようなコードがありました。ブログはrootあたりにtheme-lighttheme-darkクラスを付与してテーマを切り替えていました。そのためこのスタイルが指定されているコンポーネントはテーマによってスタイルが変わるようになっています。

const style = css` .theme-light & { color: var(--color-title-light); } .theme-dark & { color: var(--color-title-dark); } `;

これをCSS Modulesに切り替えるためには、少し工夫が必要です。というか、CSS Modulesの基本を知っていたらすぐに分かるのですが、いかんせん雰囲気でやっていたので、1回対応したときは全くうまくいかずに断念してました。

CSS Modulesにはグローバルスコープと**ローカルスコープ**があります。ローカルスコープはファイルごとにスコープが生成されるため、他のファイルのスタイルを参照できません。そのためにCSS Modulesではクラス名にハッシュを付与することでスコープを生成しています。逆に言うと、ルートにある theme-light クラスを指定したとしても、ローカルスコープで.theme-lightを指定してしまうと、別のクラス名に変換されてしまうためスタイルが適用されません

-- 実際生成されるのは .theme-light__12345 .title__67890 的な感じ .theme-light .title { color: var(--color-title-light); }

これを回避するためには、theme-lightグローバルスコープとして宣言することです。Exceptionsにて記載されていますが、 :globalという疑似属性みたいなものを使うことで、グローバルスコープとして宣言できます

:global(.theme-light) .title { color: var(--color-title-light); }

ちなみに、Claudeにファイルを投げて「CSS Modulesに変換して」とやると簡単に変換できるので良かったです。


  1. WyW-in-JSのREADMEやサイトを読んでも、いまいちbabelに依存している感はないですが、コードを呼んでみるとbabelを前提としているように見えます。私の解像度の10倍ぐらいでコードリーディングしている方がいたので、気になる方はWyW-in-JS のコードリーディングを参照してください ↩︎