モダンフロントエンドにおけるスタイリングメソッドは数多くありますが、その中でも自分はCSS Modulesをよく使います。
- HTMLとCSSをそれぞれ別ファイルに書くので関心の分離が進む
- CSSクラスのローカルスコープ化ができる (当たり前)
- 生のCSS構文が使える (当たり前)
- Tailwindをだいぶ昔にちょっと使ったきりなので知識が浅くて恐縮なのですが、例えばTailwindで複雑なセレクタを表現するには
Arbitrary Values
と呼ばれるものを使用する必要があり、ちょっとサンプルを覗いてみた感じソースコードの可読性が下がりそう(?)@apply
を使ってCSSファイルに切り出す手法もあるみたいですが、本末転倒感🤔
- Tailwindをだいぶ昔にちょっと使ったきりなので知識が浅くて恐縮なのですが、例えばTailwindで複雑なセレクタを表現するには
generateScopedName
オプションを指定することによってどのようなクラス名を要素に当てているのか本番環境では隠蔽する(=ハッシュ値のみにする)ことができる- だって要素に対してどういう命名してるのかエンドユーザーにバレるの、恥ずかしいでしょ?
- CSS Modules独自の拡張構文が使える (参考)
- 紹介した手前申し訳ないですが自分は使ったことないです🙇
あたりが主な理由なのですが、それとは別に今まで「どうにかならないかな〜」と薄々感じていたのが 型安全じゃない! ということでした。
例えばCSSファイルに.information
というクラスが存在しないのにHTMLでstyles.information
を参照していてもエラーになりませんし、逆にHTMLでstyles.information
を参照していないのにCSSファイルに.information
のスタイルが置かれていてもエラーになりません。 (後者は基本的にはTypeScriptのエコシステムに絡んでいない部分のはずなのでワガママではある)

あとはコードジャンプができないということです!
本当はクラス名をクリックしたらCSSファイルの該当箇所まで飛んでいきたいのです🕊️
これらを解決してくれるソリューション無いかな〜とやっとの思いで重い腰を上げてみたところCSS Modules Kitという素晴らしいツールがあったので、Astroプロジェクトに導入した時の作業手順を備忘録として残しておくことにします。
前提
今回は現在ご覧になっている当ブログのリポジトリにCSS Modules Kitと関連Stylelintプラグインを導入します。
フレームワークはAstroで、中に少量のAstroコンポーネントと多量のReactコンポーネントが混在している形になります。
(2025年7月にリリースしましたが、デザイン・コンテンツ移行込みで開発に1年半程度かかりました😭)
また、開発環境のエディターはVSCodeとします。
Tipsエディターについて
詳しくは公式ドキュメントをご確認いただきたいのですが、CSS Modules Kitは使用しているエディターによって導入方法が異なり、例えばVSCodeは拡張機能をインストールすれば良いだけなのに対してZedは拡張機能をインストールしたうえで設定ファイルを書き換える必要があったりします。シェア率的にVSCodeもしくはVSCodeフォーク(CursorやVSCodiumなど)のエディターを使用している方が大半でしょうから、今回はVSCode以外のエディターを使用しているケースは無いものとして考えます。
詳しくは公式ドキュメントをご確認いただきたいのですが、CSS Modules Kitは使用しているエディターによって導入方法が異なり、例えばVSCodeは拡張機能をインストールすれば良いだけなのに対してZedは拡張機能をインストールしたうえで設定ファイルを書き換える必要があったりします。シェア率的にVSCodeもしくはVSCodeフォーク(CursorやVSCodiumなど)のエディターを使用している方が大半でしょうから、今回はVSCode以外のエディターを使用しているケースは無いものとして考えます。
拡張機能をVSCodeにインストールする

mizdra
さん名義になっているCSS Modules Kit
という拡張機能をVSCodeにインストールします。
Marketplaceはこちら
codegenをインストールする
インストール完了後、npm-scripts(package.json)にcodegen実行用コマンドを登録しておきます。
tsconfig.jsonを編集する
あくまで上記はこのブログのリポジトリの場合の設定値ですが、この中で重要なのは
include
compilerOptions.rootDirs
compilerOptions.paths
の3つかなと思います。
include
公式ドキュメントには
という記載があるのですが、自分の環境ではinclude
を明示的に指定しないとcmk
を実行しても何も出力されませんでした🤔
compilerOptions.rootDirs
cmk
を実行すると(デフォルトでは)generated
ディレクトリ配下にsrcディレクトリ配下の構造がまるっとコピーされ、それぞれのディレクトリにCSS Modulesの型定義ファイルが自動生成されます。
で、今生成したgenerated/src/**/*.module.css.d.ts
からモジュールの型定義を引っ張ってきてsrc/**
にて使用する、という設定をここで指定しています。
compilerOptions.paths
インポートエイリアスを設定している場合は指定必須です。
公式ドキュメントでは特に言及されていませんが、ここでもgenerated
配下を指定に含めてあげないと、VSCodeではエラーが出るのにCI(astro check)ではエラーにならないということが起きたので忘れず指定するようにします。
codegenを実行する

おそらくここまで手順通りに実行するとCSS Modulesが型安全に扱えるようになっているかと思います! (コードジャンプもできるようになっているはずなので試してみてね)
使用されていないCSSのクラスにもエラーが出るようにする

「存在しないクラス名を指定してしまっているHTMLに対してエラーを出す」ということは実現できましたが、その逆、「使用されていないCSSのクラスに対してエラーを出す」ということがまだ実現できていないので設定していきます。
といっても設定は先述のものに比べたらめちゃくちゃ簡単で、Stylelintのプラグイン(@css-modules-kit/stylelint-plugin)をインストールし、
Stylelintの設定ファイルでextendするだけです!

するとなんということでしょう、未使用のCSSクラスに対してエラーが出るようになったではありませんか。
CSS Modules Kitに関する雑記
generatedディレクトリ配下のバージョン管理
個人的にgeneratedディレクトリ配下はバージョン管理不要だと思っているので.gitignore
に追加しています。また、直接編集することもないのでESLintやPrettierのignoreリストにも入れています。
が、こうすると例えばチーム開発においては新規参画者のcodegen未実行環境で型がハマらずにエラーになってしまうといった問題が発生することが考えられます。
npm install
とnpm run gen
を実行すれば解決可能な話ではありますが、コマンドを2つ実行するのすら億劫な方もいらっしゃるでしょう。
そういう場合は最近地味に来てる(?)Makefileでまとめちゃうのもアリなのかな〜と感じています。
元々はCやC++のプロジェクトを自前でビルドする時に見かけた程度でしたが、まあ確かに仕組み的にはフロントエンドの環境構築にも応用できるので結構マッチするのかもですね。
cmkの実行タイミング
あまりメカニズムについて詳しく把握しているわけではないのですが、VSCodeがリアルタイムでHTMLとCSSを追従してくれてるっぽいので、ホットリロードみたいな仕組みを自前で用意する必要は無さそうです。
が、プロジェクトビルド時にはやはりgenerated
ディレクトリを参照しているようなので、自分はビルドするタイミングでcmkを実行するように設定しています。
具体的にはこんな感じです。
generatedディレクトリの名前変更
CSS Modules Kitにはいくつかオプションの指定項目があり、その中に型定義の出力ディレクトリを変更するcmkOptions.dtsOutDir
というオプションが存在します。
こんな感じで出力先を指定できます。 (出力先を変更した場合はcompilerOptions
で指定しているディレクトリパスも適宜変更してください)
AstroコンポーネントだとStylelintプラグインがバグる

Reactコンポーネントの場合は全く問題ないのですが、Astroコンポーネント用に書いているCSSファイルを開くとThe corresponding component file is not found (対応するコンポーネントファイルが見つかりません)
と表示されてしまうバグが発生しています…。
内部実装を覗いてみると
となっていたのでここに.astro
を追加してみても特に状況は変わらず、真相は闇の中です🙄
ちなみにこれだとCIが通らなくなってしまうので、Stylelintの設定ファイルにて
のようにして当該ルールを無効化しています🫠
この記事は 2025/08/12 02:38:48 にビルドされました