mizdra's blog

ぽよぐらみんぐ

TSKaigi 2025 で CSS Modules Kit について発表しました

「TypeScript Language Service Plugin で CSS Modules の開発体験を改善する」というタイトルで発表しました。

speakerdeck.com

CSS Modules には、Find All References などの言語機能が動かないといった問題があります。本発表では、その問題を解決するためのツールキット「CSS Modules Kit」を紹介しました。

github.com

CSS Modules Kit は、Find All References といった主要なな言語機能をサポートしています。更に、VS Code 以外の様々なエディタで動くという特徴があります。その裏側では TypeScript Language Service Plugin と Volar.js を駆使した、非常に巧妙な実装がなされています。

CSS Modules を使っている方にとって、興味深い発表になっているかと思います。CSS Modules ユーザ以外の方にとっても、エディタの言語機能の仕組みを知ることができる、良い発表になってると思います。是非読んでみてください。

頂いた質問とその回答

X でいくつか質問頂いたので、それぞれ回答させてもらいます。

質問: codegen はどこに .module.css.d.ts を出力する?

デフォルトでは、プロジェクトルート *1 直下の generated ディレクトリにされます。例えば、以下のようなプロジェクトがある時、

project/
├─ src/
│  ├─ App.module.css
│  ├─ App.tsx
│  ├─ components/
│  │  ├─ Button.tsx
│  │  ├─ Button.module.css

codegen を実行すると、以下のように generated ディレクトリが生成されます。

project/
├─ src/
│  ├─ App.module.css
│  ├─ App.tsx
│  ├─ components/
│  │  ├─ Button.tsx
│  │  ├─ Button.module.css
├─ generated/
│  ├─ src/
│  │  ├─ components/
│  │  │  ├─ Button.module.css.d.ts
│  │  ├─ App.module.css.d.ts

ただ generated ディレクトリに生成しただけでは、tsc はその型定義を .module.css の型定義として使ってくれません。そこで CSS Modules Kit では src/** にあるモジュールの型定義を、generated/src/** から読み込むよう、tsconfig.jsonrootDirs オプションを設定する前提になってます。

happy-css-modules *2 (CSS Modules Kit の前身のツール) はデフォルトではコンポーネントの横に .module.css.d.ts を出力していて、src/ 配下に自動生成ファイルが混じって煩わしくなっていました。CSS Modules Kit ではそこが解消されていて、快適にお使いいただけるようになってます。

質問: Language Server が LPS request を受け取る優先度は変えられる?

エディタによっては変えられます! Zed はできます。というかスライドの P63 で紹介した設定がまさにそれですね。

ここでは .css の LS の優先度を vtsls (tsserver のラッパー) > それ以外の Language Server に変更してます。これにより、Zed で Rename request の取り合い問題 (P65) を回避してます。

Zed ではこれが可能ですが、残念ながら VS Code はできません。そのため VS Code では Rename request の取り合い問題が起きているのです...。NeoVim、Emacs は試してないので分からないです。設定柔軟に書けるし、多分変えられるんじゃないかなー。

ちなみに Vue.js や Astro にも CSS Modules Kit と同じように TypeScript Language Service Plugin を使った言語ツール (@vue/typescript-plugin, @astrojs/ts-plugin) がありますが、そちらでは Rename request の取り合い問題は起きていません。何故なら .vue/.astro などの独自の拡張子のファイルには、エディタ組み込みの Language Server がないからです。tsserver と Rename request を取り合う相手が居ないのです。

一方 CSS Modules Kit の場合は、.css の Language Server に tsserver と vscode-css-languageserver の2つが居ます。Rename request の取り合いは、CSS Modules Kit が .css に言語機能を提供するために起きる、特有の現象な訳です。

もしかすると VS Code 開発チームに CSS Modules Kit のユースケースを伝えれば、VS Code 側で何かしらの対策を取ってもらえるかもしれません。問題の解決策が提供されてないのは、単に今までユースケースが無かったから、というのも理由の1つだと思うんですよね。そのうち要望出そうかなと考えてます。

質問: :local():global() (CSS Modules の拡張構文) はサポートしてる?

サポートしてます!ただ、一部制限があります。:local(.button):global(.button) のように括弧を使った記法はサポートしてますが、:local .button:global .button のような括弧を使わない記法はサポートしてません。

括弧なしの記法も当初はサポートするつもりだったのですが、あまりに仕様が複雑だったので諦めました...。postcss-modules-local-by-default:local / :global の実質的な仕様になってるのですが、なんというか実装に引きずられて変な仕様になってるんですよね... 具体的にどこが変なのかは CSS Modules Kit のテストケースにメモを書いてるので、それ見てください。

びっくりする挙動が多いので、そもそもこの記法を使うべきではない、と id:mizdra は考えてます。そのため、CSS Modules Kit では意図的に括弧なしの記法をサポートしてません。

ちなみに、同じく CSS Modules の拡張構文である @value にも対応してます。@value で定義された変数は、.module.css.d.ts の型定義に含まれます。また、Bundler には @import ... で import したスタイルシートを、bundle 時に import 元へと展開する機能がありますが、それにも対応してます。@import ... で import したスタイルシートに書かれていたクラスも、.module.css.d.ts の型定義に含まれます。

CSS Modules Kit は他のツールからの乗り換えを容易にするために、エコシステムの分断を避けるために、既存のツールとの互換性を重視してます。足りない機能があったら実装を検討するので教えて下さい。:local .button:global .button のように、必ずしも実装するとは限らないですが。

質問: LSP の仕様で言語横断の言語機能を提供する仕組みが規定されていないのは何故?

言語横断の言語機能を提供したい、あるいは言語Aと言語Bの Language Server 同士で通信したい、といった要望は以前からあって、LSP の仕様リポジトリの issue で議論されてるようです。

このうち #636 のコメントを見ると、言語横断の言語機能を提供したければ、複数の言語の LS を束ねるメタ LS (proxy のようなもの) を作るよう推奨されています。CSS Modules Kit の ts-plugin もこれに分類されるものですね。

現状メタ LS のようなものがあればやりたいことは実現できますし、複雑な機能を入れるよりは、仕様をシンプルに保ちたい、という意図があるのだと思います。

また、そもそも複数の LS を束ねた時の挙動が曖昧すぎて、仕様で定めるのが難しいというのもあると思います。やろうと思えば completion response が複数の LS から返ってくるようにできますが、それのどちらを優先するのか、マージするのか、マージするにしてもどの順でマージするのか、決めようにも決めにくいところがあるはずです。ユースケースによってどうすべきか変わりますし、仕様で決めるよりはメタ LS 側で自由に決められるほうが、色々と都合が良いんじゃないかなと思います。

さいごに

当日は多くの方に発表を聴きに来ていただきました。多くの人に CSS Modules Kit の取り組みや仕組みを知ってもらえて嬉しかったです。ありがとうございました!

実は CSS Modules Kit には、発表では話せなかった面白トピックが他にも沢山あります。そちらは順次ブログに書いていこうと思います。お楽しみに。

聴いたセッションの感想はこちら。

www.mizdra.net

*1:tsconfig.json のあるディレクトリのことです。

*2:これも id:mizdra が作ったものです。https://www.mizdra.net/entry/2022/11/14/102506 を読んでください。

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.