徒然技術日記

Object.prototype.__noSuchMethod__

使われていないCSSルールを検出する stylelint-no-unused-selectors を作った

github.com

背景

発端はあるツイートより.

たしかに CSS Modules は webpack の恩恵により,styled-components や emotion の場合にはその仕組みゆえに,どれも CSS の dead code elimination (使われていない CSS ルールが最終的な CSS ファイルに入らないようにすること) は自然と行われます.

一方で, CSS in JS なアプローチを取らない場合には往々にして challenging なものとなります.既存のツールとしては UnCSS, DropCSS, PurifyCSS, PurgeCSS などがあるものの,以下のような理由から自作することにしました.

  • コンポーネントごとに CSS ファイルを分けて書いている場合 に使いづらいものが多かった
    • 入力として HTML しか対応していないもの (UnCSS, DropCSS), アプリケーション全体で使われていない CSS を検出するもの (PurifyCSS, PurgeCSS) など微妙にユースケースが合わなかった
    • コンポーネントごとに1対1で対応するテンプレートファイルと CSS ファイル間でチェックしたかった
  • jsx, tsx, CSS Modules, classnames にも対応したい
  • コードを書いているときにリアルタイムで警告されてほしい
    • stylelint の仕組みに乗っかるのが簡単そう

というわけで,以下のようなコンポーネントがある場合に,FooComponent.css と FooComponent.jsx のクラスを比較して, jsx 側で使われていないクラスがあったときに警告を出す stylelint プラグインである stylelint-no-unused-selectors が完成しました.

FooComponent
├── index.js
├── FooComponent.jsx
└── FooComponent.css

使い方

stylelint と stylelint-no-unused-selectors をインストール.

yarn add -D stylelint stylelint-no-unused-selectors

その後 .stylelintrc に設定を追加すれば動くようになります.詳しくは README を参照してください.

{
  "rules": {
    "plugin/no-unused-selectors": true
  }
}

stylelint のプラグインがエディタで有効になっていれば,未使用の CSS を発見した場合に以下のようなエラーが出るはずです.

stylelint-no-unused-selectorsによりエディタで未使用CSSが検出されている様子

ターミナルで実行するとこんな感じ.

ターミナルでstylelintを実行している様子

コード解析には @babel/parser と typescript を使っているので,TC 39 stage 4 未満の機能を使っている場合や,TypeScript で書かれている場合もサポートしています.

ちなみに600近くのコンポーネントがある某プロダクトでも試してみましたが,問題なく動作しました.

技術的な話

AST を本格的に扱うのは初めてだったので,どれから始めていいのかわからず途中まで acorn で作ってから後で @babel/parser (babylon) に移行するなど迷走してました. この界隈は玄人向けなのかあまりドキュメントがなく,かといって AST の仕様書やコードを直接読むのはかなりしんどいので,AST Explorer でいろいろいじくりまわして雰囲気で進めるのがよさそう.

JS/TS と比べると PostCSS 界隈はさらに情報が少なく,既存のプラグインのコードから挙動を推測する感じだったのが辛かったです...

おわりに

publish してから時間がなかなか取れずに記事を書けないでいたところ,なんと PostCSS の公式 Twitter が紹介してくれたのにはとても驚きました.

しっかりしたドキュメントを書くことの重要性を改めて感じました(なかなか面倒臭がってしまいますが...).記事も英語版をどこかに投稿しないと,と思っています.

主に自分たちの課題を解決するために作り始めたプラグインですが,もし便利に思っていただける方がいましたらとても嬉しく思います!