2023年6月24日土曜日

CSS 最適化ツール drop-inline-css をつよつよにした

CSS 最適化ツール drop-inline-css をつよつよにしました。 Bootstrap などで作ったサイトの CSS を最適化しながらインライン化することで、手軽に爆速化できます。

drop-inline-css

Bootstrap デカいな問題

最近の Bootstrap は 5.2.0 から CSS 変数の対応を進めているせいで、ファイルサイズの増大がやばいです。 bootstrap.min.css のサイズは 5.2.0 より前は約 160KB でしたが、いまや 228KB もあります。 そして今後のダークモードへの改善も考えると、ファイルサイズはガンガン増えることが想像できます。 なぜならどう考えても 5.3.0 では色が足りないし、色設定も足りないからです。 今後のファイル増加に備えて、以前から作っていた drop-inline-css をつよつよにすることで対応しました。

そんなの作らないでいいからカスタムビルドしようと言う人もいるかも知れません。 しかしカスタムビルドではユーティリティクラスが邪魔すぎて、削除しない限り CSS サイズが縮小しません。 しかもユーティリティクラスはレイアウトやコンポーネントにも影響力を持っているため、 削除するとカスタムビルドがまったくできなくなってしまう問題もあります。 ユーティリティクラスの拡張は CSS 変数化の一貫で進められていますが、 最適化の観点から言えば超邪魔です。 一番の問題は HTML/CSS を解析して捨てるしか最適化ができない点です。 他の人は一体どうやって対処しているのか謎です。

積極的なインライン化をデフォルトに

drop-inline-css は、以下のコマンドを打てばたいていの CSS 最適化をやってくれます。
drop-inline-css in.html > out.html
これまでは安全性を重視し、元の bootstrap.min.css を保持してインライン化していましたが、 保持しないようにすることで一気に圧縮率を上げ、Bootstrap 5.3 対策としました。 ちなみに --href オプションを使うとこれまで通り元ファイルを残すこともできます。 動作サンプルなどのページを作るときなどには役立つかも。

--href オプションなしのデフォルトで実行すると、非常に高い圧縮効果があります。 しかし JavaScript で後から使うようなユーティリティクラスも消えるので、JavaScript で操作するときには注意が必要です。 ただなるべく簡単に使えるようには考えてあり、後で使いたいユーティリティクラスは以下のように HTML にキャッシュできます。
<div class="drop-inline-css dropdown-menu show opacity-50 d-none"></div>
上記の例では d-none をして表示に影響がないようにした上で、 後で使う opacity-50 のようなユーティリティクラスを列挙してキャッシュしています。 このようなハックを利用することで問題は簡単に解決できます。 ただ現実にはこのようなハックをしないで済むように、JavaScript による動的な CSS 生成をなるべくしないようにし、 初期レンダリングを HTML になるべく記述することが大切になります。 特に面倒なのが .table-striped で、テーブルは事前に用意しておくしかないと思います。 初期レンダリングをきちんとするとユーザーフレンドリーにもなるので、実装の見直しにも良いです。

不要 CSS の削除は PurgeCSS が安定

ところで、そもそもなぜ bootstrap.min.css を保持していたかというと、内部で使っていた DropCSS が完全な動作をしなかったからです。 今後は元ファイルを保持しないと決めてから問題を細かくチェックしたところ、CSS 変数をきちんと扱えなかったようで、Bootstrap 5.3.0 では .table-striped などがうまく動きませんでした。 ちなみに CSS 変数はかなり扱いが難しく、きちんとサポートできているツールがあるのかよくわからないです。 キー自体も変数化でき、順序によって意味も変わるなど、かなりややこしいので仕方ない気もします。 ただ、難しいなら複雑なことは無視したり、段階的に処理できたほうが良いことは間違いありません。

そこで一番採用率の高い PurgeCSS に切り替えてみたところ、そのへんの設計がしっかりしていて、現状では一番良さそうでした。 ただ細かい最適化はきちんと動かないので、安心して使える部分だけ利用します。 CSS 変数はまだ動かないです。 JavaScript からの purge も完璧に動作させるのは難しそうです。 @keyframes は私が使った限りではバグを感じなかったですが、エラー報告されているのでひとまず様子見をしています。

Bootstrap 5 の定義リスト

初期レンダリングの改良では対応できないプロパティも多少はあります。 さきほどの例で dropdown-menu show の記述は重要で、Bootstrap のドロップダウンを使う時に必要です。 ドロップダウンは初期状態では非表示がほとんどなので、表示状態を事前定義しないと使えません。 同じように動きのあるコンポーネントには事前定義が必要です。 調査済みのコンポーネント別 Bootstrap 定義リストをメモしておきます。 こういったプロパティは自動指定できれば良いのですが、あまり良い方法はなさそうです。
<div class="drop-inline-css
  dropdown-menu show
  modal modal-static
  modal-backdrop fade show
  carousel-item-prev carousel-item-next
  carousel-item-start carousel-item-end
  dropdown-toggle show
  dropdown-menu show dropdown-item
  toast showing
  collapsing
  d-none">
</div>

Offcanvas

上記は指定の簡単なものですが、面倒なものもいくつかあります。たとえば Offcanvas。 以下の指定でいけるのですが、これはあまり良い指定方法ではありません。
offcanvas-backdrop fade show showing hiding
上記の showing と hiding は bootstrap.min.css の中では、.showing 単体では定義されておらず、.offcanvas.showing, .offcanvas.hiding といった形で定義されています。 そのため PurgeCSS が更新され積極的な最適化を行うようになると、削除されてしまう可能性があります。 上記の指定は最適化がゆるいので許されているだけの状態です。 といって offcanvas showing hiding のような形で登録すると、Boostrap の JavaScript がページの初期化時に backdrop を表示してしまいます。

そこで以下の指定方法が良いと思います。 この指定のほうが PurgeCSS や Bootstrap が更新した時に影響を受けにくいです。
<div class="drop-inline-css
  offcanvas-backdrop fade show
  offcanvas showing hiding
  d-none" data-bs-backdrop="false" data-bs-scroll="true">
</div>

Tooltip

Tooltip は、さらに記載が面倒です。 どこに表示するかを JavaScript で検知し、data-popper-placement 属性で管理しているため、それをすべて定義しておかないといけません。 JavaScript 処理が増えると指定が面倒くさいですね。
<div class="drop-inline-css
  tooltip tooltip-arrow tooltip-inner bs-tooltip-auto fade show
  bs-tooltip-top bs-tooltip-end bs-tooltip-bottom bs-tooltip-start
  d-none">
  <div data-popper-placement="left"></div>
  <div data-popper-placement="top"></div>
  <div data-popper-placement="right"></div>
  <div data-popper-placement="bottom"></div>
</div>
他にも popover は指定が必要と思いますが、使ったことがないので未調査です。


vs Tailwind

Tailwind も内部で PurgeCSS を使っているので、JavaScript 部分はどうしているのだろうと思って調べてみると、whitelist で対処しているようです。 これが意味するところは、まずは JavaScript からの purge は厳しいということでしょう。 そして whitelist についてですが、複雑な指定が難しく、ファイルごとへの最適化が難しく、表記の揺らぎへの対処が難しく、マッチ方法が色々あって困るなど、多くの欠点があると個人的には思っています。 今のところ HTML へのキャッシュのほうが楽でいいと思っています。 HTML へのキャッシュも複雑な指定をしにくい問題はありますが、複雑ならばコンポーネント化したほうが良く、そこに記述すべきではないかという気がします。


最後に drop-inline-css を使うどどのような効果があるか、 marmooo.github.io のページを例に挙げてみましょう。 通常なら index.html 34KB, bootstrap.min.css 228KB で表示されますが、 完全にインライン化された現在は、index.html 45KB だけで表示できます。 つまり CSS は 11KB しかないということです。 読み込み時間に関しては PageSpeed Insigts だと 0.6秒くらいは短縮できます。 同じドメインの 100 くらいのアプリと数千ページにも適用して実験しましたが、問題は見つかりませんでした。 0.6秒の改善が手動だと大変ですが、drop-inline-css は一度設定すれば自動でやってくれるので気楽です。

0 件のコメント: