CSSのルールセットから重複する定義の削除

CSSWringに同じルールセット内で重複する定義(プロパティーとその値のセット)をルールセットから削除する機能をつけてる。最初、重複するプロパティーを問答無用で削除しようかと考えたんだけど、同じプロパティーを同じルールセット内でわざと使うことは結構あるのでダメそうだった。なので完全一致で削除するようにしようとしてる。

PostCSSでCSSをパースすると、各定義は以下のような構造になる(一部省略)。

{
  type: 'decl',
  before: '\n  ',
  prop: 'color',
  between: ': ',
  value: 'white'
}

typeに定義であることが明記されていて、propでプロパティー名、valueで値が拾える。またbeforeにプロパティー名の前にある文字列、betweenにプロパティーと値の間の文字列が入ってる。

一致のチェックはpropvalueを単純に連結させ、それをキーにしてその定義のインデックスをオブジェクトに格納していき、hasOwnPropertyで探すという形で良さそう。見つかったら重複しているとみなし、格納したインデックスを使って古い方を削除し、新しい方のみ残すようにする。

PostCSSでは全定義をなめるイテレータ(eachDecl())もあるけど、この場合はルールセット単位で作業するべきなのでeachRule()の方で行う。

var postcss = require('postcss');
var css = fs.readFileSync('test.css', 'utf-8');
var root = postcss.parse(css);
root.eachRule(function (rule) {
  var d = '';
  var decls = {};
  rule.each(function (decl, index) {
    d = decl.prop + decl.value;

    if (decls.hasOwnProperty(d)) {
      decl.parent.remove(decls[d]);
    }

    decls[d] = index;
  });
});

通常はこのようにpropvalueが一致すれば良いけど、CSSWringには一部CSSハックを維持する設定がある。この設定がONの時にこれでは判別できない。対応しているCSSハックが格納されているのはbeforebetweenなので、それらも含めて完全一致させる必要があった。空白の違いにより重複が判別できなくなりそうだけど、CSSWringでは空白文字の圧縮は事前に行われているのでそこは考えなくて良さそう。上記コードだと8行目を以下のようにするだけ。

d = decl.before + decl.prop + decl.between + decl.value;

最初hasOwnPropertyではなくdecls[d]で存在チェックをしていて、インデックスが0の定義が重複していた時にちゃんとチェックできてないみたいなのやらかした……。