PythonでCSSを全力で小さくする！

# ごあいさつ
こんにちは、~~非リア系~~高校生のLaddgeです。

明日から高3になります。~~留年回避万歳~~

最近Web系の開発をよくやっていて、先日リリースした静的サイトジェネレーター「[tanuky](https://github.com/laddge/tanuky)」についての記事は、たくさんの人に見ていただけて本当に嬉しいです。  
ありがとうございます。

気が向いたらそっちも見てみてください↓

https://qiita.com/laddge/items/bd3fd8d7be8ca7d7873d

# 「[PageSpeed Insights](https://pagespeed.web.dev)」で100点を取りたい
Web開発をしている人なら、一度は見たことがあると思います。

https://pagespeed.web.dev

このサイトでは、URLを入力するだけで、そのサイトの読み込みスピードなどを計測して点数形式で結果を表示してくれます。

ためしに、Qiitaのトップページを計測するとこんな感じになります。

![BA3CAF2F-ED28-47C2-8818-2074E39287A4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637875/dbf4377b-5c7e-ed9a-b06c-69a3eef8cc5d.png)

点数の下には、改善点なども挙げてくれるのでとてもありがたいです。

個人規模のサイトだったら、80点くらいは普通に取れます。

「いやどうせなら100点取りたくねww」

# Bootstrapが戦犯
みなさんBootstrap使ってますか？  
僕はゴリゴリ使ってます。

(Bootstrapの説明は割愛します。わからない人はググってください)

Bootstrap使うと楽なんですよ、やっぱり。  
でもここに大きな落とし穴があって、こういう簡単に書けるよ的なフレームワークはどうしてもCSSのサイズが膨大になってしまって、ページの読み込みが遅くなってしまいます。  
🥺

# 優等生になるための道のり
では、実際にさまざまな方法でスピードアップしていきます。

## CSSの非同期ロード (=> 失敗)
でかいCSSを読み込む時に、そのままだとCSSを読んでいる間はページの読み込みをブロックしてしまいます。

そこで、CSSを非同期で読み込むというやり方があります。(まあ結構無理やり感ありますが)

```html:before.html
...
<link rel=“stylesheet” href=“style.css”>
...
```

普通はこうやって読み込むと思います。(少なくとも僕の中ではこれがデフォルト)

これを非同期で読むようにするとこうなります。

```html:after.html
...
<link rel="preload" href="style.css" as="style">
<link rel="stylesheet" href="style.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="style.css"></noscript>
...
```

最初の2行は、どちらも非同期に読み込むという役割をします。  
1行目のpreload属性を使ったやり方だと、古いブラウザだと対応していなかったりするので、2行目のmedia=“print”を使ったやり方も書いておきます。  
これは、「印刷用のCSSは非同期に読み込まれる」という特性を使って実現しています。  
ただし、ファイルを読み込んだあと普通のCSSとして認識されるために、JavaScriptで属性を書き換えます。  
これだと、preloadに対応していない、かつ、JavaScriptが無効化されている場合にCSSが適用されないので、3行目の&lt;noscript&gt;タグ内に普通に読み込むタグを書いておきます。

Web開発はこういう対応していないブラウザのためのフォールバックが結構めんどくさいですよね。~~IEは捨てましょう~~

さて、見出しに失敗と書きました。何が問題だったでしょう。

非同期に読み込むことで、ページの読み込みにかかる時間は短くなりました。が、点数は90点弱くらいにとどまりました。

原因は、ページが表示されてからCSSが適用されるまでの間にラグが生まれたことです。まあ、考えてみれば当たり前です。

ページに当てるCSSをほとんどBootstrapに頼っていたので、CSSが適用されたタイミングでUIが大きく動いてしまいます。  
これにより、CLSという値が悪くなってしまいました。

https://web.dev/cls

まあ、こいつの点数以前に、一瞬サイトの裸を晒すのは少し恥ずかしいです。きゃっ

そんなわけで、CSSの非同期読み込みは却下しました。

## Sassを使う
Bootstrapには、使う機能を絞ったり、変数の値を変えたりして自分好みのCSSをビルドするためのSassが用意されています。

Sassというのは、CSSの拡張版のようなもので、専用のツールでCSSに変換して使うことができる形式です。  
詳しくはググってください。(ごめんなさい)

今回は、これを使って、実際にサイト内で使われている機能に絞ったミニマルバージョンのBootstrap CSSをビルドして使ってみます。

CSSは余分な改行やスペースを取り除いて1行に縮小できるので、その処理も同時にしてしまいます。(この処理をminifyといいます)

|ファイル|サイズ|
|-|-|
|bootstrap.min.css (公式のやつ)|152.2 KB|
|custom.css (ビルドしたやつ)|90.5 KB|

4割ほど削減できました。

これにより、非同期読み込みをしなくてもそこそこ早く読み込めて、点数は95点くらいまで上がりました。

うーん、どうせなら100点取りたいよなあ

## 使ってないクラスなどの記述を削除
Sassでのカスタマイズだと、分割されたファイルのうち必要なものだけインポートする感じですが、これでもまだ使ってないクラスなどの記述が残っています。

例えば、ボタンに関するファイルをインポートして使っているとして、HTMLのほうでは青いボタンと赤いボタンしか使っていないのに、CSSには緑やオレンジのボタンについても書かれている、というイメージです。(実際はもっと複雑なのもあります)

こういう無駄を省くプログラムを書いてしまいましょう。  
いらないCSSの記述を消すツールは既にあります。

* [PurgeCSS](https://purgecss.com/)
* [uncss](https://github.com/uncss/uncss)

などが有名どころです。

ところが、こういうツールの多くはNode.js製です。まあ、HTMLやCSSとの相性がいいからですかね。

でも僕はPython大好き…サイトのジェネレーターとしてもPython製のtanukyを使っている…

ここにNode.jsを無理やり組み込むのはなんか負けた感あるなあ、ということで、

PythonでCSSをめっちゃ小さくするライブラリを作ってしまいましょう。  
(やっとタイトル回収です)

# 作ったもの
作ったライブラリはいつも通りGitHubに公開しています。

https://github.com/laddge/reducss

今回はだいぶ雑に作ったのでバージョニングとかドキュメントとかめっちゃ適当ですがお許しください。

仕組みとしては、

1. CSSファイルから、セレクタと、そのセレクタに当たっている記述がファイルの何文字目から何文字目まで書いてあるかの一覧を取得
2. 各セレクタについて、HTMLに対応する要素があるか判別
3. 対応する要素がない = 必要ない記述の位置を記録
4. いらない記述を削除

という感じです。

あまり外部ライブラリに頼りたくなかったので正規表現などでゴリゴリ削ります。

(2だけは流石にめんどいのでBeautifulSoupの力を借りました)

もちろんminifyもしてくれます。

まあ、さすがにJavaScriptで後から追加した要素とかまでは意識してないので、うまく動かないサイトもあるかもです。

# 結果発表
![3BA3EE82-DED9-470C-ABD3-7A0B4F014A05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637875/4dc72b0e-0ddb-cc11-15a2-207881da4016.png)

無事100点出ました。

CSSのファイルサイズは驚異の **7.06KB** ！  
何も手を加えていない公式のCSSが 152.2KB だったので95%も削減できました。

まあ、試したのが作りかけの小規模サイトなのもあると思いますが、とはいえものすごい進歩です。

# さいごに
最後まで読んでいただきありがとうございました。  
明日から高3生として頑張ろうと思います。