未使用の CSS を 整理、削除してくれるツールがないかと探したところ、 UnCSS というツールを見つけました。
GitHub - giakki/uncss: Remove unused styles from CSS
試しにこのブログに適用してみた結果を記載します。

[TOC]


参考


使用する動機

現状、このブログは、Django 製の CMS Mezzamine を使っていて、CSS 関連の最適化は以下のような処理になっています。

この状況だと、Lint ツールで、読み<wbr>込んでいる CSS の<wbr>サイズが<wbr>大きいのでなんとかしろ警告が出ていて、なんとかしたいと思っていたというのが動機です。

このサイトに UnCSS どのように組み込むか

CSS パフォーマンスツールを使いこなす - ワザノバ | wazanova記載を引用します。

  • 削除可能なCSSの判定は100%の精度ではない。 動的に挿入されるCSSの対応もサポートしているが、エッジケースも多いので要注意。必ずテストすること。

  • 「今のページで必要なCSSだけ使う。」「他のページで使われているスタイルは無視する。」よりも、「サイト全体に適用されるCSSで、どこでも使われていないものはどれか」という考え方をすべき。

2点を考慮して、 CSS 最適化の作業を以下の通り実施しようかと考えました。1

  • UnCSS で CSS の整理 1. URL ごとに UnCSS で使用している CSS を抽出
    2. 1.抽出した CSS は Minify 状態なので、見やすく整形する 3. 2.作成した CSS をマージして、サイト全体で適用されている CSS のみ抽出

  • コーディング
    1.CSS、CSS を使用するマークアップの作成、変更

  • アプリケーション側での処理
    1.GitHub - django-compressor/django-compressor使って、CSS を圧縮、結合

2.GitHub - martinblech/django-critical使って、Critical CSS 作成 と loadCSS で 1. CSS の読み込みを行う。


UnCSS で CSS の整理する 作業

以下の通り作業を実施していきました。

0. 必要なツールのインストール

  • UnCSS をインストール

    npm install -g uncss
    

  • cssbeautify-cli をインストール

    npm install -g cssbeautify-cli
    

1. URL ごとに UnCSS で使用している CSS を抽出

まず、サイトの URL のリストを作成しないといけないです。 このサイトの URL は、domain 直下からリンクのあるページ、sitemap.xml に記載されている URL を抽出すれば全ページになるため Bash でスクリプトを書いて URL を抽出しました。

スクリプトは、gist に UP しましたので、サイト全ての HTML に uncss cssbeautify-cli を実行するスクリプトそちらをご確認ください。

2. 1.抽出した CSS は Minify 状態なので、見やすく整形する

1. gist を実行すると、以下のコマンドが標準出力に出力されます。

uncss 'https://www.monotalk.xyz/blog/Set-anchor-text-of-Wicket-BookmarkableLink/' -t 5000 -s https://www.monotalk.xyz/static/CACHE/css/690a0d57c104.css | cssbeautify-cli -s > 'https:www.monotalk.xyzblogSet-anchor-text-of-Wicket-BookmarkableLink.css'
UnCSS で 整理された CSS を標準出力後に、cssbeautify-cli で Minify を解除しています。
400ページ程度に対してのコマンドの実行に 1 時間程度、かかりました。

3. 2.作成した CSS をマージして、サイト全体で適用されている CSS のみ抽出

【Mac】Xcode付属のファイル比較ソフトFileMerge | atnr.net使って、出力された CSS の手動マージを行いました。
さすがに全部とはいかず、いくつかページを抜粋して差分の具合を確認して、差分の傾向から以下の方針でマージは実施しました。

  • 方針

    • normalize css 、codehighlight.css はそのまま使う。
      normalize.css と、コードハイライトに使用している codehighlight.css は、サイズも大きくないので定義の削除はせずにそのまま使用することにしました。
      normalize.css は、bootstrap.css含まれていたので、改めてダウンロードし、サーバ上に配置しました。

    • bootstrap.cssclean_blog.cssfont-awesome.min.cssfonts.css整理の対象にする。
      このサイトは、Clean Blog - Bootstrap Blog Theme - Start Bootstrapベースに作っています。
      テーマ関連の css と、サーバ上に配置している font-awesome.min.cssfonts.css整理の対象としました。


UnCSS で CSS の整理前、整理後の変化

CSS のサイズ

5分の1 程度になりました。

  • *UnCSS 適用前 *

    Content-Length:27718
    

  • *UnCSS 適用後 *

    Content-Length:5914
    

Coachアドバイスの変化

CSS のサイズが 14.5KB 以下 になったので、optimalCssSizeアドバイスが適用前は出力されていましたが、出力されなくなりました。

├─────────────────────────┼───────────────────────────────────────────────────────┼──────────┤
│ optimalCssSize          │ https://www.monotalk.xyz/static/CACHE/css/690a0d57c1… │ 90       │
│                         │ size is 27.7 kB (27718) and that is bigger than the   │          │
│                         │ limit of 14.5 kB. Try to make the CSS files fit into  │          │
│                         │ 14.5 kB.                                              │          │
├─────────────────────────┼───────────────────────────────────────────────────────┼──────────┤


UnCSS の Tips

実際に使用してみて、得られた Tips を記載します。

adsbygoogle.js例外が発生するが、CSS の出力には影響がない

UnCSS でコマンド実行中に以下のような例外が発生しますが、特に CSS の出力には影響はないので、無視して OK でした。

Error: Uncaught [TagError: adsbygoogle.push() error: No slot size for availableWidth=0]
    at reportException (/usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:58:24)
    at processJavaScript (/usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/living/nodes/HTMLScriptElement-impl.js:130:7)
    at HTMLScriptElementImpl._eval (/usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/living/nodes/HTMLScriptElement-impl.js:65:7)
    at /usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/browser/resource-loader.js:31:22
    at Object.check (/usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/living/nodes/Document-impl.js:89:11)
    at Object.check (/usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/living/nodes/Document-impl.js:92:23)
    at Object.check (/usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/living/nodes/Document-impl.js:92:23)
    at Object.check (/usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/living/nodes/Document-impl.js:92:23)
    at Object.check (/usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/living/nodes/Document-impl.js:92:23)
    at /usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/living/nodes/Document-impl.js:108:12
    at wrappedEnqueued (/usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/browser/resource-loader.js:255:16)
    at Request.request [as _callback] (/usr/local/lib/node_modules/uncss/node_modules/jsdom/lib/jsdom/browser/resource-loader.js:203:9)
    at Request.self.callback (/usr/local/lib/node_modules/uncss/node_modules/request/request.js:186:22)
    at emitTwo (events.js:125:13)
    at Request.emit (events.js:213:7)
    at Request.<anonymous> (/usr/local/lib/node_modules/uncss/node_modules/request/request.js:1163:10) Q {
  message: 'adsbygoogle.push() error: No slot size for availableWidth=0',
  stack: 'TagError: adsbygoogle.push() error: No slot size for availableWidth=0\n    at Rg (https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js:1:35360)\n    at Sg (https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js:1:34287)\n    at $h (https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js:1:57771)\n    at fi (https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js:1:60958)\n    at gi (https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js:1:60883)\n    at Ai (https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js:1:66408)\n    at Bi.c.client (https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js:1:64709)\n    at Od (https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js:1:14673)\n    at Xd (https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js:1:17244)\n    at Object.Bi [as push] (https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js:1:64688)',
  name: 'TagError' }

UnCSS: no stylesheets found エラーになる

このブログだと、オプションなし実行だと Error: UnCSS: no stylesheets foundエラーになりました。
おそらく、loadCSS使っているのが影響してるのかと思います。

Error: UnCSS: no stylesheets found
    at processWithTextApi (/usr/local/lib/node_modules/uncss/src/uncss.js:134:15)
    at tryCatcher (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/util.js:16:23)
    at Promise._settlePromiseFromHandler (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/promise.js:509:35)
    at Promise._settlePromise (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/promise.js:569:18)
    at Promise._settlePromise0 (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/promise.js:614:10)
    at Promise._settlePromises (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/promise.js:693:18)
    at Promise._fulfill (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/promise.js:638:18)
    at PromiseArray._resolve (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/promise_array.js:126:19)
    at PromiseArray._promiseFulfilled (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/promise_array.js:144:14)
    at Promise._settlePromise (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/promise.js:574:26)
    at Promise._settlePromise0 (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/promise.js:614:10)
    at Promise._settlePromises (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/promise.js:693:18)
    at Async._drainQueue (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/async.js:133:16)
    at Async._drainQueues (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/async.js:143:10)
    at Immediate.Async.drainQueues (/usr/local/lib/node_modules/uncss/node_modules/bluebird/js/release/async.js:17:14)
    at runCallback (timers.js:781:20)
これは、-s オプションで解析対象の CSS を指定することで回避しました。
uncss 'https://www.monotalk.xyz/blog/strbuilder-%E3%81%8Capache-commons-lang-%E3%81%8B%E3%82%89-apache-commons-text-%E3%81%AB%E7%A7%BB%E5%8B%95%E3%81%97%E3%81%9F/' -s https://www.monotalk.xyz/static/CACHE/css/690a0d57c104.css

-t、 –timeout オプションについて

JS の実行を待つオプションです。-t 5000指定していますが、CSS の出力変化までは確認していません。
DOM の Load 後に、CSS 追加する javascript があると出力が変化するのかと思われます。

CssSyntaxError が出力される

構文的に間違った CSS が含まれると、CssSyntaxError: uncss/node_modules/css: unable to parse undefined:いうエラーが発生します。
調べたところ、以下の Issue が見つかりました。
構文的に間違った CSS はそもそも読み込むことができないということです。
Ignore error · Issue #275 · giakki/uncss
まず、何よりも先に、構文エラーは直す必要があります。


まとめ

UnCSS を 使って、ブログに適用している CSS を整理してみました。
以下まとめます。

  • 重量級の CSS ライブラリの削減には効果がある。
    CSS 容量は、5分の1となりました。ほとんど Bootstrap の CSS が削除されたので、効果はあるかと思います。

  • ページパターンが少なければ、それほど適用は難しくない。 このブログは、ログイン状態、未ログイン状態が存在せず、表示の動的な切り替えは行っていないため、適用はスムーズでした。2
    会員サイト等の場合、UnCSS はログイン機能は持たないため、未ログイン状態だけ適用する等の工夫は必要に思いました。

  • 歴史を抱えた CSS の整理に使う。
    長年メンテナンスしているサイトの場合、無駄に CSS が肥大化している場合があります。
    心機一転して、CSS を作り直す際にまず適用するのはありかと思います。

  • 重要なランディングページのみに適用。
    サイト全体の CSS に適用するのがベストですが、リスクがあり、コストがかかりすぎる場合もあります。
    重要なランディングページのみに適用するのは、理想の使い方からは外れるかもしれないですが、そちらの方が ROI は良さそうに思いました。

以上です。


  1. django-critical が行う処理の前に動的に処理することもできそうですが、必ずテストはしなさいと記載があり、手動で作成することにしました。 

  2. ログイン画面は Mezzanine デフォルトのCSSを使っているので影響は受けませんでした。 

コメント