Astro と CSS Modules を使用した React のコンポーネントの、開発と本番時における style の優先順の差異

Astro のコンポーネントでは、:where() 疑似クラスを使用したカプセル化を行うため、詳細度が高くなりません。そのため CSS Modules を使っている React のコンポーネントと合わせて使用する場合、開発環境と本番環境でその優先順位に差異が発生するケースがあります。
2022.11.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

すこし前の話になるのですが、SSG している自分のブログNext.jsからAstroに置き換えました。置き換えた経緯などはそちらで記事にしています。そのためここでは、Astro コンポーネントのスタイルについての詳細には言及していません。

今回発生した現象修正した PR としては以下になります。

経緯

自分のブログを時間があるときに少しずつ機能実装しているのですが、あるとき本番環境でスタイルが崩れていることに気づきました。

アイコンと文字が並列で表示されていることが期待されているが、文字が段落ちしてしまった画像

結構目立つスタイルの崩れだったので、なぜ開発中に気づかなかったのか疑問に思いながら開発環境を立ち上げると、現象の再現はしませんでした。

本番環境 開発環境
アイコンと文字が並列で表示されていることが期待されているが、文字が段落ちしてしまった画像 アイコンと文字が並列に表示されている

調査

CSS なので調査は難しくありません、Devtools で確認してみます。

本番環境 開発環境
display: blockが優先されている display: flexが優先されている

本番環境では次のようになっています。

  • index.6061e360.css ファイルが生成されていて、どちらも同じファイル内で記述されている
  • display: block; が指定されている ._root_r8td5_1 はファイル内で後に記述され優先されている
  • display: flex; が指定されている .menu:where(.astro-YPTKBFHU) > * はファイル内で先に記述され上書きされている

開発環境では次のようになっています。

  • display: flex; が指定されている ._root_r8td5_1styles.module.css ファイルに記述され、上書きされている
  • display: block; が指定されている .menu:where(.astro-YPTKBFHU) > *style 要素に記述され、優先されている

詳細度は同じである

._root_r8td5_1.menu:where(.astro-YPTKBFHU) > * の詳細度はともに 0,1,0 となっています。一見 .menu:where(.astro-YPTKBFHU) > * の詳細度が高いように思えますが、全称セレクタは重みの計算にカウントされません。

詳細度を細かく数値として Devtools で確認することができないので、計算ツールを用いることが多いです。ただし検索するとでてくるツールで、まれに :where() に未対応のものがあったりするので注意が必要です。ここでは Polypane の CSS Specificity Calculator をオススメしておきます。

次に優先されるもの

CSS の優先順位については以前書いた詳細度よりも優先される CSS の Cascade Layers についてという記事で解説しています。

開発環境ではその origin の違いから style 要素に記述されている CSS が適用され、本番環境では同一ファイル内での出現順序の違いにより、後に記述されている CSS が適用されているという現象が発生していることが確認できました(ビルド結果での出現順位は常に固定というわけではなさそう)。

結論

Astro のコンポーネントでは、:where() 疑似クラスを使用したカプセル化を行うため、詳細度が高くなりません。そのため CSS Modules を使っている React のコンポーネントと合わせて使用する場合、開発環境と本番環境でその優先順位に差異が発生するケースがありそうです。

今回のケースでは全称セレクタを、具体的な要素名に変更する対応をとることで一時的な解決は可能です。しかしそれは根本的な解決策ではなく、Astro コンポーネントから React のコンポーネントへの CSS の干渉を避けるべきであるように感じました。