min-widthを使ったクエリーのみをソートするオプションを追加し、CSS MQPacker v3.1.0をリリースした。sortオプションを有効にすると、まとめられたクエリーのうちmin-widthが使われるクエリーのみソートされる。

オプションの切り替えに対応するため、新たに引数を渡してインスタンスを作成できるようになった。今までの使われ方を壊さないように実装したので、今までのコードはそのまま動く。

var mqpacker = require('css-mqpacker');

var result = mqpacker({
  sort: true
}).pack(css);
console.log(result.css);

ソート機能を有効にするにはsortオプションをtrueにしてインスタンスを作成してやる。PostCSSのプロセッサー(プラグイン)として使う場合も同じだ。PostCSSのオプションに混ぜ込むことも可能だが、将来的な安全は保証されていないので推奨しない。なおデフォルトでは無効のため、明示的に有効にしてやる必要がある。

@media (min-width: 2em) {}
@media print {}
@media (min-width: 48px) {}
@media (min-width: 16px) {}
.foo {}
@media (min-width: 48px) {}
@media (max-width: 8px) {}
@media (min-width: 2em) {}
@media (min-width: 16px) {}

例えばこのようにmin-widthクエリーが散在し、それらが複数の単位を用いている上、他の種類のクエリーも混ざっているようなケースを処理してみよう。ありそうもないように思えるが、CSSプリプロセッサーで@mediaルールをネストして書いていると似たような構成にはなりうるはずだ。

.foo {}                     /* .foo {} */
@media (min-width: 16px) {} /* @media (min-width: 2em) {} */
@media (min-width: 2em) {}  /* @media print {} */
@media (min-width: 48px) {} /* @media (min-width: 48px) {} */
@media print {}             /* @media (min-width: 16px) {} */
@media (max-width: 8px) {}  /* @media (max-width: 8px) {} */

sortオプションを有効にして処理すると、このように同じメディア・クエリーがまとめられ、後ろに回された上で、min-widthクエリーのみがソートされる。わかりやすいようにsortオプションを指定しない場合の順序もコメントで併記しておいた。

px以外の単位はなんとなくpx単位に変換(フォントの初期設定が16pxでArialとみなして変換を行う)されてソートされるので、複数の単位が混ざっていてもそれなりに機能する。inptなど環境に強く依存する単位はサポートしていないので、無視されソートされることはない。min-width以外にprintメディア・タイプなども含め特別視することはないので、それらは今まで通り出現順のまままとめられるというわけだ。

現状ではmin-widthのみ、せいぜいそれに加えてprintのみを使って書くのが主流と考えられるので、大抵は問題なく適切な状態に処理されることと思う。なにかおかしいところが見つけたら是非報告して欲しい


sortオプションには直接関数も指定することができる。こちらを使うと自由にソートすることができる。

var result = mqpacker({
  sort: function (a, b) {
    return a.localeCompare(b);
  }
}).pack(css);

例えばString#localeCompareを使うと、雑に文字コード順でソートすることができる。

作成した関数は、完全なクエリーの配列に対するArray#sortへと渡される。例えばテストにあるsort_ignore-print-queries.cssだと以下のような配列になる。

[
  "(min-width: 2em)",
  "print and (min-width: 1em)",
  "print, (min-width: 3em)",
  "(min-width: 1em)"
]

これをちゃんと処理しなければならないので、カンマ区切りの処理やand区切りの処理も自前で用意する必要がある。min-widthだけでもかなり大変だったので、かなり苦労することと思う。そこそこちゃんとパースした結果を渡すというのも悪くはないと考えていたが、どちらかというとPostCSSというかAST側でやって欲しい機能なので、ツール側で持ってしまうのはやめておいた。

このソート関数の作成は汎用化しようとするとかなり大変な労力を必要とする。特化型で作るのなら簡単だが、その場合はCSS側でソート順を定義してやる方が更に手軽だったりもする。そのためまず出番はないことと思うがどうしてもmax-widthで書きたいという場合などには役に立つかもしれない。


ソートする機能の実装はとにかく面倒臭かった。print and (min-width: 1px)は無視して良さそうだが、print, (min-width: 1px)は無視するべきではなさそうだ、など、普段まともにCSSを書いている場合にはまったく考える必要のないエッジ・ケースで時間を浪費したので、ストレスが溜まった。既にいくつか見つけてしまったマイナーな解決できそうもない問題やMedia Queries Level 4の範囲指定クエリーのこととかで更に気が重い。それらが表面化する頃には、多数のファイルでメディア・クエリーを分散させつつ投げつけても怒られないHTTPS + HTTP/2時代になっていることに期待したい。

このウェブサイトでも同梱しているGruntプラグインのテストがてら稼働させ始めた。メディア・クエリーを抜き出すと以下のような順序で最後に追加されている。

@media (min-width: 352px) {}
@media (min-width: 39.118rem) {}
@media (min-width: 53.487rem) {}
@media not screen {}
@media print {}

ちゃんと動いていそうだ。