エメラルドマウンテンの麓。

CleanCSSとmerge-media-queryでCSSのminifyを徹底的にやってみた

clean-css ignore:start指定について追記しました。(2018/10/4)

当ブログでは CSS の minify に CleanCSS を使用しています。この度はまだまだ minify 出来ることに気づいたので腰を据えて設定を詰めました。

併せて導入した gulp-merge-media-queries のお陰で冗長な @media をゼロにできました。その上、これまでは同一の @media 毎に拡張コメントで囲んで独自ツールで並び替えていましたが、これが不要なことが分かりました。

  1. CleanCSS の minify 設定がデフォルトだったのを最高レベルに。
  2. gulp-merge-media-queries を導入して冗長な @media を無くす。

これによりファイルサイズを2割近く削減することが出来ました。

CleanCSSの最高レベルのminify設定

最高レベルの minify 結果を得るために、パラメータを書き換えつつ出力結果を確認する、を繰り返す必要がありました。詳しいパラメータは CleanCSS@Github に掲載されています。

gulp.js の一部

この度、到達した設定は次の通りです。

cleanCSS({
  level: {
    1: {
      // rounds pixel values to `N` decimal places; `false` disables rounding; defaults to `false`
      roundingPrecision : 3
    },
    2: {
      // controls duplicate `@font-face` removing; defaults to true
      removeDuplicateFontRules: true,
      // controls duplicate `@media` removing; defaults to true
      removeDuplicateMediaBlocks: true,
      // controls duplicate rules removing; defaults to true
      removeDuplicateRules: true,
      // controls semantic merging; defaults to false
      mergeSemantically: true,
      // controls unused at rule removing; defaults to false (available since 4.1.0)
      removeUnusedAtRules: true,
      // controls rule restructuring; defaults to false
      restructureRules: true
    }
  }
});

1. ignore 指定の追加

CleanCSS の level:2 では重複する CSS プロパティを削除し、複数のプロパティをマージします。レガシーブラウザ向けに敢えて重複させたり、冗長にしているプロパティを保護するため、clean-css ignore:startclean-css ignore:end を追加しました。今回この指定が必要だったのは本件では次のプロパティに対してでした。

-webkit-transition

古い WebKit ブラウザ用に -webkit-transition-property を分けたのをマージから保護します。

a:link, a:visited {
    background-color            : rgba( 0, 0, 0, 0 );
/* clean-css ignore:start */
    -webkit-transition-property : background-color, color, border-color;
            -webkit-transition  : 0.3s ease-in;
/* clean-css ignore:end */
               -moz-transition  : background-color 0.3s ease-in, color 0.3s ease-in, border-color 0.3s ease-in;
                 -o-transition  : background-color 0.3s ease-in, color 0.3s ease-in, border-color 0.3s ease-in;
                    transition  : background-color 0.3s ease-in, color 0.3s ease-in, border-color 0.3s ease-in;
}

Data URI スキームで埋め込んだテスト用のWebフォント

Webフォントの可否を調べるために小さな Web フォントを Data URI スキームで埋め込んでいました。この Web フォントは javascript で利用するため、.scss 中ではどこからも font-family: で参照されていませんでした。この為に Web フォントが削除されてしまうのを保護しました。

2. リンクのインタラクションの副作用

CleanCSS では適宜に指定を纏めて出現順を入れ替えます。この為にリンクのインタラクション周りで、出現順に依存する曖昧な指定を行っていた場合、不具合に遭遇しやすいようです。

:link, :visited それぞれに、:hover, :focus, :active, :focus:hover, :active:hover を設定していきます。本ブログの記事『リンクテキストのインタラクションを整理する』もご参考に、タブフォーカスなど突き詰めていくと結構冗長なコードが必要になります。

:link, :visited { text-decoration : underline; }
:link:hover , :visited:hover,
:link:focus , :visited:focus,
:link:active, :visited:active { text-decoration : none; }
:link:focus:hover, :visited:focus:hover,
:link:active:hover, :visited:active:hover { text-decoration : underline !important; }

gulp-merge-media-queriesで@mediaの重複を無くす

CleanCSS でも @media をマージしてくれますが完全ではありませんでした。今回 merge-media-queries を導入すると @media の重複を完全に無くすことができました。

単位を統一することでメディアクエリを正しく並び替えてくれます。(max-width: ...), (min-width: ...) が混在していても大丈夫でした。メディアクエリは通常は px 単位が使われますが、当ブログではフォントサイズベースのレイアウトの為、em を使用しています。

handheld, projection, tv, only screen and (min-width: 801px){...}
↓
handheld, projection, tv, only screen and (min-width: 50.06em){...}

当初、merge-media-queries を CleanCSS の前に実行しましたが、冗長な @media が残ってしまいました。CleanCSS である程度マージしたものを merge-media-queries に渡すフローにすると冗長の無い CSS が得られました。

この段階では空白文字が残るため、CleanCSS をもう一度使ってこれを除きました。この際には level:1 で空白文字だけを除去する設定にします。

gulp.js の全体、最初の cleanCSS() のパラメータは省略してある

var gulp     = require('gulp'),
    sass     = require("gulp-sass"),
    cleanCSS = require("gulp-clean-css"),
    plumber  = require("gulp-plumber"),
    mmq      = require("gulp-merge-media-queries");

gulp.task('sass', function() {
  return gulp.src(['R:/precompiled/*.scss'])
    .pipe(plumber())
    .pipe(sass())
    .pipe(cleanCSS({
      level: {
        1: {...},
        2: {...}
      }
    }))
    .pipe(mmq())
    .pipe(cleanCSS({ 
      level: {
        1: {
          // set all values to `false`
          all: false,
          // controls removing unused whitespace; defaults to `true`
          removeWhitespace: true
        }
      }
    }))
    .pipe(gulp.dest('./dest'));
});