😁

型がないCSS Modules(scss)の型を自動生成して快適に開発する

2024/03/24に公開

はじめに

最近Next.js(App router)を使いはじめており、CSS in JSとの相性があんまり良くないため、
CSS Modulesを使っています。
ただ、CSS in JSと異なり、型がありません。なので誤ったクラスを使っていても特にエラーを検知できないので、PRレビューや実際の環境でのレビューで検知する必要があります。
これでは運用工数が増えてしまうため、調べていたところCSS Modulesのクラス名の型を自動で生成してくれるツールを見つけたので今回はこちらを試してみようと思います。

typed-scss-modules

もともとはtyped-css-modulesをいれる予定だったのですが、scssファイルに対応していなさそうだったので、こちらをためしてみます。

導入

開発環境下のみの使用なので、-Dオプションをつけてインストールします。

npm i -D typed-css-modules

設定ファイル

設定をカスタマイズする方法は2通り存在します。
基本の設定はconfigに書き、環境によって設定を動的に変えたい場合はCLIのオプションで渡す方針にします。

  • 引数で渡す
typed-scss-modules src --watch --ignoreInitial
  • 設定ファイルを配置する。
    ルートディレクトリにtyped-scss-modules.config.tstyped-scss-modules.config.jsファイルを配置する事でも可能です。
export const config = {
  banner: "// customer banner", // 生成された型ファイルにコメントが入ります。
  exportType: "default", 
  exportTypeName: "TheClasses",
  logLevel: "error",
};

つかってみる

--watchオプションを付与することでscssファイルを変更したら自動で再生成されるようになります。

npm-run-all2を用いて、devスクリプトを実行するとnextのサーバとtyped-scss-modulesの監視が行われるようにしています。

{
  "private": true,
  "scripts": {
    "dev": "run-p dev:next dev:scss",
    "dev:next": "next dev --turbo --port 8081",
    "dev:scss": "typed-scss-modules src --watch",
    "build": "next build",
    "build:scss": "typed-scss-modules src",
    "start": "next start"
  },
  "dependencies": {
    "next": "14.1.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "zod": "3.22.4"
  },
  "devDependencies": {
    "npm-run-all2": "6.1.2",
    "typed-scss-modules": "8.0.0",
    "sass": "1.72.0",
  }
}

オプションのカスタマイズ

出力先ディレクトリの変更

デフォルトだと、scssファイルがあるディレクトリと同階層に型定義ファイルが生成されてしまうので、仮にライブラリの使用をやめたい!となった場合にすぐ消せるようにしたいので、出力されるディレクトリを変更します。

export const config = {
  exportType: 'default',
  nameFormat: 'none',
  implementation: 'sass',
  outputFolder: "__generated__/typed-scss-modules",
    ignoreInitial: true, // 起動時の初回生成をスキップする
};

あ、tsconfigにも設定加えてあげないとうごきません!

{
  "compilerOptions": {
    "rootDirs": [".", "__generated__/typed-scss-modules"], // 追加生成先に指定したディレクトリを追加
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  },
}

これでscssファイルに変更を加えると以下のログが表示されて指定されたディレクトリに型定義ファイルが生成されるようになります。

✓ Ready in 1126ms
[CHANGED] /src/app/page.module.scss
[GENERATED TYPES] /__generated__/typed-scss-modules/src/app/page.module.scss.d.ts

はい、無事に生成されました。

先生、型が生成されていなかったら事前に検知したいです

無事に自動生成できました!
これでめでたしめでたしと言いたいところですが、このままでは、CSS Modulesのファイルだけ型が作られていることを保証できていません。
「scssファイルを追加したのに型定義が生成されてなかったらコードレビューで弾けばいい!」とお考えかもしれませんが、そんなことにレビューやルールを考える時間を費やしたくはありません。

幸いにも「--listDifferent」というオプションが用意されているので、こちらを使って検知できます。
package.jsonにスクリプトを追加します。

"lint:typed-scss": "typed-scss-modules src --listDifferent",

これを使って、scssファイルのみある状態でスクリプトを実行すると以下のようなエラーメッセージを表示してくれます。

[INVALID TYPES] Type file needs to be generated for /app/src/app/page.module.scss 
 ELIFECYCLE  Command failed with exit code 1.

page.module.scssが存在するのに型定義が存在してないので怒っています。
開発者は、「はい、今すぐ作ります!」と声高らかに叫び、すぐさま型を生成してくれるでしょう。

後は、CIやhuskyなどにこのスクリプトを組み込むなどしてあげれば、CSSと型定義ファイルがずれることはなくなるはずです。

懸念

主にVSCodeユーザー向けなのですが、型が生成されたことにより、コードジャンプ先が型定義になってしまいます。css-modulesプラグインを入れることで、ジャンプ先にcssファイルが表示されるようになります。型定義と重複して表示されていますが、そこは目で見て判断してあげてください。

happy-css-modulesというコードジャンプ先をcssファイルに変更してくれる専用のライブラリも存在するようですが、上記のプラグインでも十分かなと考えたので、今回は導入を見送りました。

最後に

CSS in JSの型がある開発は魅力的で、CSS Modulesでも同様の体験ができればと考えていたので、今回紹介したライブラリを作ってくれた開発者にはとても感謝です!

ここまで読んでいただいてありがとうございました!

SMARTCAMP Engineer Blog

Discussion