Closed21

styled-components で書かれたReactComponentPackageを CSS Modules に変更する

keitaknkeitakn

やる事 & 前提条件の説明

猫のLGTM画像共有サービス LGTMeow を運用しています。

https://lgtmeow.com

UIのスタイリングは styled-components で作られていますが、CSS Modulesに置き換えます。

このサービスは去年デザイン変更をしています。その時の記事は下記になります。

https://zenn.dev/keitakn/articles/lgtmeow-design-renewal

上記の記事を見ると分かるのですが、色々な経緯でUIPackageとアプリケーション側が分かれているので今回主に変更するのは下記のUI側のpackageのほうになります。

https://github.com/nekochans/lgtm-cat-ui

やろうと思った経緯

Next.js 13.4から有効になった AppRouter では大きな変更点の1つとして Server Components が利用可能になっています。

しかし下記のドキュメントを見ると分かるように styled-components のようなJSランタイムを必要とするCSS in JSライブラリはServer Componentsで利用できません。

https://nextjs.org/docs/app/building-your-application/styling/css-in-js

最初はゼロランタイムのCSS in JS ライブラリである vanilla-extract を利用する予定でしたが調査したところ、何とか動作はするけど、公式にサポートされていると名言されている訳ではないので一番無難なCSS Modulesを使う事にしました。

以下はその時の調査結果をまとめたスクラップです。

https://zenn.dev/keitakn/scraps/b872604237472e

仕事でも同じ構成のpackageを運用しているので、まずは個人開発で運用しているpackageから試してみる事にしました。

これが終わったら LGTMeow も AppRouterへの移行を進めていく予定です。

keitaknkeitakn

最初の一歩

src/styles/mixins.ts という共有のCSSをまとめたファイルがあります。

これは styled-componentscss 関数で作られているのですが、これと同じ役割を持つ src/styles/globals.css を作成しました。

Buttonのスタイリングがここに入っている事に違和感がありますが、このあたりは後で直します。

:root {
  --primary-color: #eb7c06;
  --primary-variant-color: #f0a14e;
  --variant-color: #ffd184;
  --sub-color: #f2ebdf;
  --sub-variant-color: #fffcf6;
  --text-color: #362e2b;
  --sub-text-color: #8e7e78;
  --white-color: #ffffff;
  --background-color: #faf9f7;
  --media-query-default-size: 767px;
}

.button-base {
  display: flex;
  flex: none;
  flex-direction: row;
  flex-grow: 0;
  gap: 10px;
  align-items: center;
  order: 1;
  width: fit-content;
  padding: 7px 20px;
  cursor: pointer;
  border-radius: 4px;
  box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
}

.button-base:hover {
  opacity: 0.8;
}

.button-text {
  flex: none;
  flex-grow: 0;
  order: 1;
  font-family: Roboto, sans-serif;
  font-size: 16px;
  font-style: normal;
  font-weight: 400;
  line-height: 16px;
  color: var(--sub-color);
}

.button-text:hover {
  opacity: 0.8;
}

このCSSを利用してまず最初にFooterのComponentを置き換える事にしました。

その時にやった対応は下記のPRの通りです。

https://github.com/nekochans/lgtm-cat-ui/pull/272

typed-css-modules を使ってCSS Modulesの型定義を生成しています。これにより存在しないクラス名などを指定した際に気がつけるようになっています。

この状態でBuildを実施すると dist/style.css が生成されます。

しかし少し問題が発生しました。

  • dist/style.csssrc/styles/globals.css の内容が反映されていない
  • dist/style.css が公開されていないので packageの利用側で以下のようにimportする必要があった
import 'node_modules/@nekochans/lgtm-cat-ui/dist/globals.css';
import 'node_modules/@nekochans/lgtm-cat-ui/dist/style.css';

対策としてpackageのエントリーポイントである src/index.ts から src/styles/globals.css を読み込むようにしました。

これで dist/style.css に必要なCSSが全て反映されます。

さらに import '@nekochans/lgtm-cat-ui/style.css'; でCSSをimport出来るように package.json に設定を追加しました。この時の対応が下記のPRになります。

https://github.com/nekochans/lgtm-cat-ui/pull/275

これでpackage利用側のアプリケーションの src/pages/_app.tsx に以下を追加する事で正常に今までと同じFooterのデザインが反映されるようになりました。

import '@nekochans/lgtm-cat-ui/style.css';
keitaknkeitakn

デザイン崩れをどうやって検知するか?

Chromatic というサービスを使ってStorybookをデプロイしています。

このサービスはStorybookを利用したビジュアルリグレッションテスト(VRT)も可能になっています。

これを利用してデザイン崩れが発生していない事を担保しています。

デザインに差分がある場合は以下のように差分が閲覧出来ます。

この仕組みのおかげでデグレを恐れずにデザイン変更が出来ますが、無料プランなのでビジュアルリグレッションテスト(VRT)の実行回数に上限があります。具体的には月に5000回のスナップショットを上限としていてそれを超えるとビジュアルリグレッションテスト(VRT)が出来なくなります。(Storybookのデプロイは可能です)

ビジュアルリグレッションテスト(VRT)がないとデザイン崩れを確認するのが辛いので、もしもスナップショットの上限に達してしまった場合は一時的に課金するか、Playwright + reg-suit 等の別のツールでビジュアルリグレッションテスト(VRT)を実行する等の対応が必要になると思っています。

自分の場合は使える時間が限られているので一時的に課金する方向で乗り切ろうかと思っています。

keitaknkeitakn

置換えを効率的に行う為に生成AIを利用する

Browse with Bingを有効にしたChatGPTを活用しています。

私は本格的にプロンプトエンジニアリングを本格的には学んでいませんが、十分に置換え対象のComponentを吐き出してくれます。

私はChatGPTに対して課金を行っているのでGPT4のモデルを使ったり、Browse with Bingを有効に出来ますが、おそらく無料プランでGPT3.5を利用していてもこのくらい単純な変更であれば問題なく行う事が出来ると思います。

keitaknkeitakn

HeaderComponentの置換え

UIで言うと以下の部分を置換えていく。

keitaknkeitakn

LanguageMenu の置換え

以下が対応時のPR。

https://github.com/nekochans/lgtm-cat-ui/pull/277

若干リンクの箇所でデザイン崩れが発生したのでChatGPTが提案してきたコードをベースにしつつリファクタリングを行った。

と言ってもほとんど問題なく置き換える事が出来た。

keitaknkeitakn

LanguageButtonの置換え

こちらが対応時のPR。

https://github.com/nekochans/lgtm-cat-ui/pull/280

こちらも問題なく置換えは出来たのだが、1つ新しい発見があった。

CSS変数はメディアクエリ内では利用出来ないらしい。

調べてみたがCSSの仕様で、メディアクエリはパース時に解釈され、変数は後で解決されるので参照出来ない事が理由らしい。

なので一旦ハードコードで対応した。

VRTでも検出されなかった理由はレスポンシブデザインだったから、Storybookの viewport を変更して初めて気がつく事が出来た。(当たり前だがVRTも万能ではない)

正しいデザイン

collect

崩れたデザイン(メディアクエリ内でCSS変数が使えないのでブレークポイントが設定されておらず崩れている)

incorrect

keitaknkeitakn

直接関係はないが この記事 を見てるとレスポンシブやる時は今後はコンテナクエリはを使ったほうが良いのか?等も思った。

このデザインはFigmaのデータをインスペクトして得られたCSSをベースにしているのでメディアクエリで実装している。(FigmaがメディアクエリでCSSを吐き出す為)

Figmaの設定をいじってコンテナクエリベースのCSSを吐き出すように変更出来るのか?とか一瞬頭によぎったが、脱線しそうなので今はやめておこうと思う。

keitaknkeitakn

あとFigmaが吐き出した物をベースにしているのもあって元々のマークアップの構造が微妙だったのでChatGPTに色々とアクセシビリティの事を聞いてみた。

対応したほうが良いとは思ったが本筋の目的からズレてしまうのでissue化して後ほど対応しようと思う。

https://github.com/nekochans/lgtm-cat-ui/issues/279

keitaknkeitakn

Button系のComponentを置換え

以下が対応時のPR、特に難しい事はなかった。強いて言えばCSSのbackgroundイメージ画像を指定する際に url(/src/components/Button/images/slash.png) のようにフルパスで指定する必要がある事くらい。

https://github.com/nekochans/lgtm-cat-ui/pull/295

keitaknkeitakn

画像アップロード系のComponentの作成

UploadForm があまりにも大きいComponentなのでPRを2つに分割した。

https://github.com/nekochans/lgtm-cat-ui/pull/298

https://github.com/nekochans/lgtm-cat-ui/pull/299

ここまでChatGPTに置換えを依頼していたが、ここまで大きいComponentになるとChatGPTも一回でこちらが欲しいComponentを生成する事は難しく何度かやり取りが必要だった。

やはりComponentが大きいと変更に対して弱いので、時間がある時にリファクタリングが必要だと感じた。

とは言えChromaticのVRTのおかげで変更時の検証はかなり楽だと感じた。

keitaknkeitakn

Stylelint、ESLint の設定から styled-components に関連するルールを削除

Stylelintの設定変更

stylelintの設定 & package構成を変更し通常のCSSをターゲットにするように変更。

今までは styled-components を利用していたので https://github.com/kufu/stylelint-config-smarthr にお世話になっていたがこれは styled-components をベースにした物なので削除しました。(今までありがとうございます。)

ルールは極力シンプルにしたかったので stylelint-config-recess-orderstylelint-config-prettier だけを利用するようにした。

ESLintの設定変更

eslint-plugin-styled-components-varname を削除した。

以下が対応時のPR。整形結果に結構差分が出てしまったので、移行をするという意志決定をした時に早めに対応すれば良かったなと思った。

https://github.com/nekochans/lgtm-cat-ui/pull/306

keitaknkeitakn

styled-components の削除

以下のPRを参照。

https://github.com/nekochans/lgtm-cat-ui/pull/307

最初にCSS Modules の置き換えで不要になったファイルを削除、置き換え忘れのComponentがあったのでそれも一緒に置き換えてある。

置き換え忘れのComponentは一個だったので同じPRでやってしまったが複数あった場合はPRを分けたほうが良いと思う。

次に styled-components の削除、ドキュメントからも styled-components の記述を削除している。

https://github.com/nekochans/lgtm-cat-ui/pull/309

keitaknkeitakn

リリース

スタイリングを CSS Modules に変更したバージョンのpackageをリリース。

https://github.com/nekochans/lgtm-cat-ui/pkgs/npm/lgtm-cat-ui/98021986

アプリケーション側は最新のUI Package(@nekochans/lgtm-cat-ui)を利用するようにして styled-components への依存箇所を削除。

https://github.com/nekochans/lgtm-cat-frontend/pull/254

アプリケーション側にも少しだけ styled-components でスタイリングされていた部分があったのでそれもCSS Modules に置き換えている。

CSS Modules の型定義だが今回は happy-css-modules というpackageを利用した。

使ってみた感想としては単純に typed-css-modules の上位互換と言っても良いので今後はこちらを使おうと思う。

https://www.mizdra.net/entry/2022/11/14/102506

https://github.com/mizdra/happy-css-modules

思ったより時間がかかってしまったがこれでCSS Modules への移行は完了。

今後はApp Routerへの移行を進めていく予定。

このスクラップは2023/06/02にクローズされました