Adds value & charset optimizations.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Wed, 1 Oct 2014 09:59:35 +0000 (10:59 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Fri, 10 Oct 2014 20:22:45 +0000 (21:22 +0100)
* Re-implemented most of property values' minifications.
* Extra charset(s) are stripped and first one is moved to the beginning.

13 files changed:
lib/colors/hex-name-shortener.js [new file with mode: 0644]
lib/colors/hsl-to-hex.js [deleted file]
lib/colors/hsl.js [new file with mode: 0644]
lib/colors/long-to-short-hex.js [deleted file]
lib/colors/rgb-to-hex.js [deleted file]
lib/colors/rgb.js [new file with mode: 0644]
lib/colors/shortener.js [deleted file]
lib/selectors/optimizer.js
lib/selectors/optimizers/simple.js
test/colors/hex-name-shortener-test.js [new file with mode: 0644]
test/integration-test.js
test/selectors/optimizer-test.js
test/selectors/optimizers/simple-test.js

diff --git a/lib/colors/hex-name-shortener.js b/lib/colors/hex-name-shortener.js
new file mode 100644 (file)
index 0000000..914b235
--- /dev/null
@@ -0,0 +1,181 @@
+function HexNameShortener(value) {
+  this.value = value;
+}
+
+var COLORS = {
+  aliceblue: '#f0f8ff',
+  antiquewhite: '#faebd7',
+  aqua: '#0ff',
+  aquamarine: '#7fffd4',
+  azure: '#f0ffff',
+  beige: '#f5f5dc',
+  bisque: '#ffe4c4',
+  black: '#000',
+  blanchedalmond: '#ffebcd',
+  blue: '#00f',
+  blueviolet: '#8a2be2',
+  brown: '#a52a2a',
+  burlywood: '#deb887',
+  cadetblue: '#5f9ea0',
+  chartreuse: '#7fff00',
+  chocolate: '#d2691e',
+  coral: '#ff7f50',
+  cornflowerblue: '#6495ed',
+  cornsilk: '#fff8dc',
+  crimson: '#dc143c',
+  cyan: '#0ff',
+  darkblue: '#00008b',
+  darkcyan: '#008b8b',
+  darkgoldenrod: '#b8860b',
+  darkgray: '#a9a9a9',
+  darkgreen: '#006400',
+  darkgrey: '#a9a9a9',
+  darkkhaki: '#bdb76b',
+  darkmagenta: '#8b008b',
+  darkolivegreen: '#556b2f',
+  darkorange: '#ff8c00',
+  darkorchid: '#9932cc',
+  darkred: '#8b0000',
+  darksalmon: '#e9967a',
+  darkseagreen: '#8fbc8f',
+  darkslateblue: '#483d8b',
+  darkslategray: '#2f4f4f',
+  darkslategrey: '#2f4f4f',
+  darkturquoise: '#00ced1',
+  darkviolet: '#9400d3',
+  deeppink: '#ff1493',
+  deepskyblue: '#00bfff',
+  dimgray: '#696969',
+  dimgrey: '#696969',
+  dodgerblue: '#1e90ff',
+  firebrick: '#b22222',
+  floralwhite: '#fffaf0',
+  forestgreen: '#228b22',
+  fuchsia: '#f0f',
+  gainsboro: '#dcdcdc',
+  ghostwhite: '#f8f8ff',
+  gold: '#ffd700',
+  goldenrod: '#daa520',
+  gray: '#808080',
+  green: '#008000',
+  greenyellow: '#adff2f',
+  grey: '#808080',
+  honeydew: '#f0fff0',
+  hotpink: '#ff69b4',
+  indianred: '#cd5c5c',
+  indigo: '#4b0082',
+  ivory: '#fffff0',
+  khaki: '#f0e68c',
+  lavender: '#e6e6fa',
+  lavenderblush: '#fff0f5',
+  lawngreen: '#7cfc00',
+  lemonchiffon: '#fffacd',
+  lightblue: '#add8e6',
+  lightcoral: '#f08080',
+  lightcyan: '#e0ffff',
+  lightgoldenrodyellow: '#fafad2',
+  lightgray: '#d3d3d3',
+  lightgreen: '#90ee90',
+  lightgrey: '#d3d3d3',
+  lightpink: '#ffb6c1',
+  lightsalmon: '#ffa07a',
+  lightseagreen: '#20b2aa',
+  lightskyblue: '#87cefa',
+  lightslategray: '#778899',
+  lightslategrey: '#778899',
+  lightsteelblue: '#b0c4de',
+  lightyellow: '#ffffe0',
+  lime: '#0f0',
+  limegreen: '#32cd32',
+  linen: '#faf0e6',
+  magenta: '#ff00ff',
+  maroon: '#800000',
+  mediumaquamarine: '#66cdaa',
+  mediumblue: '#0000cd',
+  mediumorchid: '#ba55d3',
+  mediumpurple: '#9370db',
+  mediumseagreen: '#3cb371',
+  mediumslateblue: '#7b68ee',
+  mediumspringgreen: '#00fa9a',
+  mediumturquoise: '#48d1cc',
+  mediumvioletred: '#c71585',
+  midnightblue: '#191970',
+  mintcream: '#f5fffa',
+  mistyrose: '#ffe4e1',
+  moccasin: '#ffe4b5',
+  navajowhite: '#ffdead',
+  navy: '#000080',
+  oldlace: '#fdf5e6',
+  olive: '#808000',
+  olivedrab: '#6b8e23',
+  orange: '#ffa500',
+  orangered: '#ff4500',
+  orchid: '#da70d6',
+  palegoldenrod: '#eee8aa',
+  palegreen: '#98fb98',
+  paleturquoise: '#afeeee',
+  palevioletred: '#db7093',
+  papayawhip: '#ffefd5',
+  peachpuff: '#ffdab9',
+  peru: '#cd853f',
+  pink: '#ffc0cb',
+  plum: '#dda0dd',
+  powderblue: '#b0e0e6',
+  purple: '#800080',
+  rebeccapurple: '#663399',
+  red: '#f00',
+  rosybrown: '#bc8f8f',
+  royalblue: '#4169e1',
+  saddlebrown: '#8b4513',
+  salmon: '#fa8072',
+  sandybrown: '#f4a460',
+  seagreen: '#2e8b57',
+  seashell: '#fff5ee',
+  sienna: '#a0522d',
+  silver: '#c0c0c0',
+  skyblue: '#87ceeb',
+  slateblue: '#6a5acd',
+  slategray: '#708090',
+  slategrey: '#708090',
+  snow: '#fffafa',
+  springgreen: '#00ff7f',
+  steelblue: '#4682b4',
+  tan: '#d2b48c',
+  teal: '#008080',
+  thistle: '#d8bfd8',
+  tomato: '#ff6347',
+  turquoise: '#40e0d0',
+  violet: '#ee82ee',
+  wheat: '#f5deb3',
+  white: '#fff',
+  whitesmoke: '#f5f5f5',
+  yellow: '#ff0',
+  yellowgreen: '#9acd32'
+};
+
+var toHex = {};
+var toName = {};
+
+for (var name in COLORS) {
+  var hex = COLORS[name];
+  if (name.length < hex.length)
+    toName[hex] = name;
+  else
+    toHex[name] = hex;
+}
+
+HexNameShortener.prototype.shorten = function () {
+  var data = this.value;
+
+  [toHex, toName].forEach(function(conversion) {
+    var pattern = '(' + Object.keys(conversion).join('|') + ')';
+    var colorSwitcher = function(match, colorValue, suffix) {
+      return conversion[colorValue.toLowerCase()] + suffix;
+    };
+    data = data.replace(new RegExp(pattern + '( |,|\\)|$)', 'ig'), colorSwitcher);
+  });
+
+  return data;
+};
+
+module.exports = HexNameShortener;
diff --git a/lib/colors/hsl-to-hex.js b/lib/colors/hsl-to-hex.js
deleted file mode 100644 (file)
index 6bb5031..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-module.exports = function HSLToHex(data) {
-  // HSL to RGB converter. Both methods adapted from:
-  // http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
-  var hslToRgb = function(h, s, l) {
-    var r, g, b;
-
-    // normalize hue orientation b/w 0 and 360 degrees
-    h = h % 360;
-    if (h < 0)
-      h += 360;
-    h = ~~h / 360;
-
-    if (s < 0)
-      s = 0;
-    else if (s > 100)
-      s = 100;
-    s = ~~s / 100;
-
-    if (l < 0)
-      l = 0;
-    else if (l > 100)
-      l = 100;
-    l = ~~l / 100;
-
-    if (s === 0) {
-      r = g = b = l; // achromatic
-    } else {
-      var q = l < 0.5 ?
-        l * (1 + s) :
-        l + s - l * s;
-      var p = 2 * l - q;
-      r = hueToRgb(p, q, h + 1/3);
-      g = hueToRgb(p, q, h);
-      b = hueToRgb(p, q, h - 1/3);
-    }
-
-    return [~~(r * 255), ~~(g * 255), ~~(b * 255)];
-  };
-
-  var hueToRgb = function(p, q, t) {
-    if (t < 0) t += 1;
-    if (t > 1) t -= 1;
-    if (t < 1/6) return p + (q - p) * 6 * t;
-    if (t < 1/2) return q;
-    if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
-    return p;
-  };
-
-  return {
-    process: function() {
-      return data.replace(/hsl\((-?\d+),(-?\d+)%?,(-?\d+)%?\)/g, function(match, hue, saturation, lightness) {
-        var asRgb = hslToRgb(hue, saturation, lightness);
-        var redAsHex = asRgb[0].toString(16);
-        var greenAsHex = asRgb[1].toString(16);
-        var blueAsHex = asRgb[2].toString(16);
-
-        return '#' +
-          ((redAsHex.length == 1 ? '0' : '') + redAsHex) +
-          ((greenAsHex.length == 1 ? '0' : '') + greenAsHex) +
-          ((blueAsHex.length == 1 ? '0' : '') + blueAsHex);
-      });
-    }
-  };
-};
diff --git a/lib/colors/hsl.js b/lib/colors/hsl.js
new file mode 100644 (file)
index 0000000..5c76b6e
--- /dev/null
@@ -0,0 +1,67 @@
+// HSL to RGB converter. Both methods adapted from:
+// http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
+
+function HSLColor(hue, saturation, lightness) {
+  this.hue = hue;
+  this.saturation = saturation;
+  this.lightness = lightness;
+}
+
+function hslToRgb(h, s, l) {
+  var r, g, b;
+
+  // normalize hue orientation b/w 0 and 360 degrees
+  h = h % 360;
+  if (h < 0)
+    h += 360;
+  h = ~~h / 360;
+
+  if (s < 0)
+    s = 0;
+  else if (s > 100)
+    s = 100;
+  s = ~~s / 100;
+
+  if (l < 0)
+    l = 0;
+  else if (l > 100)
+    l = 100;
+  l = ~~l / 100;
+
+  if (s === 0) {
+    r = g = b = l; // achromatic
+  } else {
+    var q = l < 0.5 ?
+      l * (1 + s) :
+      l + s - l * s;
+    var p = 2 * l - q;
+    r = hueToRgb(p, q, h + 1/3);
+    g = hueToRgb(p, q, h);
+    b = hueToRgb(p, q, h - 1/3);
+  }
+
+  return [~~(r * 255), ~~(g * 255), ~~(b * 255)];
+}
+
+function hueToRgb(p, q, t) {
+  if (t < 0) t += 1;
+  if (t > 1) t -= 1;
+  if (t < 1/6) return p + (q - p) * 6 * t;
+  if (t < 1/2) return q;
+  if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+  return p;
+}
+
+HSLColor.prototype.toHex = function () {
+  var asRgb = hslToRgb(this.hue, this.saturation, this.lightness);
+  var redAsHex = asRgb[0].toString(16);
+  var greenAsHex = asRgb[1].toString(16);
+  var blueAsHex = asRgb[2].toString(16);
+
+  return '#' +
+    ((redAsHex.length == 1 ? '0' : '') + redAsHex) +
+    ((greenAsHex.length == 1 ? '0' : '') + greenAsHex) +
+    ((blueAsHex.length == 1 ? '0' : '') + blueAsHex);
+};
+
+module.exports = HSLColor;
diff --git a/lib/colors/long-to-short-hex.js b/lib/colors/long-to-short-hex.js
deleted file mode 100644 (file)
index 87fa31b..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-module.exports = function LongToShortHex(data) {
-  return {
-    process: function() {
-      return data.replace(/([,: \(])#([0-9a-f]{6})/gi, function(match, prefix, color) {
-        if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5])
-          return prefix + '#' + color[0] + color[2] + color[4];
-        else
-          return prefix + '#' + color;
-      });
-    }
-  };
-};
diff --git a/lib/colors/rgb-to-hex.js b/lib/colors/rgb-to-hex.js
deleted file mode 100644 (file)
index acfbf01..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-module.exports = function RGBToHex(data) {
-  return {
-    process: function() {
-      return data.replace(/rgb\((\-?\d+),(\-?\d+),(\-?\d+)\)/g, function(match, red, green, blue) {
-        red = Math.max(0, Math.min(~~red, 255));
-        green = Math.max(0, Math.min(~~green, 255));
-        blue = Math.max(0, Math.min(~~blue, 255));
-
-        // Credit: Asen  http://jsbin.com/UPUmaGOc/2/edit?js,console
-        return '#' + ('00000' + (red << 16 | green << 8 | blue).toString(16)).slice(-6);
-      });
-    }
-  };
-};
diff --git a/lib/colors/rgb.js b/lib/colors/rgb.js
new file mode 100644 (file)
index 0000000..2f94548
--- /dev/null
@@ -0,0 +1,16 @@
+function RGB(red, green, blue) {
+  this.red = red;
+  this.green = green;
+  this.blue = blue;
+}
+
+RGB.prototype.toHex = function () {
+  var red = Math.max(0, Math.min(~~this.red, 255));
+  var green = Math.max(0, Math.min(~~this.green, 255));
+  var blue = Math.max(0, Math.min(~~this.blue, 255));
+
+  // Credit: Asen  http://jsbin.com/UPUmaGOc/2/edit?js,console
+  return '#' + ('00000' + (red << 16 | green << 8 | blue).toString(16)).slice(-6);
+};
+
+module.exports = RGB;
diff --git a/lib/colors/shortener.js b/lib/colors/shortener.js
deleted file mode 100644 (file)
index 337a727..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-module.exports = function Shortener(data) {
-  var COLORS = {
-    aliceblue: '#f0f8ff',
-    antiquewhite: '#faebd7',
-    aqua: '#0ff',
-    aquamarine: '#7fffd4',
-    azure: '#f0ffff',
-    beige: '#f5f5dc',
-    bisque: '#ffe4c4',
-    black: '#000',
-    blanchedalmond: '#ffebcd',
-    blue: '#00f',
-    blueviolet: '#8a2be2',
-    brown: '#a52a2a',
-    burlywood: '#deb887',
-    cadetblue: '#5f9ea0',
-    chartreuse: '#7fff00',
-    chocolate: '#d2691e',
-    coral: '#ff7f50',
-    cornflowerblue: '#6495ed',
-    cornsilk: '#fff8dc',
-    crimson: '#dc143c',
-    cyan: '#0ff',
-    darkblue: '#00008b',
-    darkcyan: '#008b8b',
-    darkgoldenrod: '#b8860b',
-    darkgray: '#a9a9a9',
-    darkgreen: '#006400',
-    darkgrey: '#a9a9a9',
-    darkkhaki: '#bdb76b',
-    darkmagenta: '#8b008b',
-    darkolivegreen: '#556b2f',
-    darkorange: '#ff8c00',
-    darkorchid: '#9932cc',
-    darkred: '#8b0000',
-    darksalmon: '#e9967a',
-    darkseagreen: '#8fbc8f',
-    darkslateblue: '#483d8b',
-    darkslategray: '#2f4f4f',
-    darkslategrey: '#2f4f4f',
-    darkturquoise: '#00ced1',
-    darkviolet: '#9400d3',
-    deeppink: '#ff1493',
-    deepskyblue: '#00bfff',
-    dimgray: '#696969',
-    dimgrey: '#696969',
-    dodgerblue: '#1e90ff',
-    firebrick: '#b22222',
-    floralwhite: '#fffaf0',
-    forestgreen: '#228b22',
-    fuchsia: '#f0f',
-    gainsboro: '#dcdcdc',
-    ghostwhite: '#f8f8ff',
-    gold: '#ffd700',
-    goldenrod: '#daa520',
-    gray: '#808080',
-    green: '#008000',
-    greenyellow: '#adff2f',
-    grey: '#808080',
-    honeydew: '#f0fff0',
-    hotpink: '#ff69b4',
-    indianred: '#cd5c5c',
-    indigo: '#4b0082',
-    ivory: '#fffff0',
-    khaki: '#f0e68c',
-    lavender: '#e6e6fa',
-    lavenderblush: '#fff0f5',
-    lawngreen: '#7cfc00',
-    lemonchiffon: '#fffacd',
-    lightblue: '#add8e6',
-    lightcoral: '#f08080',
-    lightcyan: '#e0ffff',
-    lightgoldenrodyellow: '#fafad2',
-    lightgray: '#d3d3d3',
-    lightgreen: '#90ee90',
-    lightgrey: '#d3d3d3',
-    lightpink: '#ffb6c1',
-    lightsalmon: '#ffa07a',
-    lightseagreen: '#20b2aa',
-    lightskyblue: '#87cefa',
-    lightslategray: '#778899',
-    lightslategrey: '#778899',
-    lightsteelblue: '#b0c4de',
-    lightyellow: '#ffffe0',
-    lime: '#0f0',
-    limegreen: '#32cd32',
-    linen: '#faf0e6',
-    magenta: '#ff00ff',
-    maroon: '#800000',
-    mediumaquamarine: '#66cdaa',
-    mediumblue: '#0000cd',
-    mediumorchid: '#ba55d3',
-    mediumpurple: '#9370db',
-    mediumseagreen: '#3cb371',
-    mediumslateblue: '#7b68ee',
-    mediumspringgreen: '#00fa9a',
-    mediumturquoise: '#48d1cc',
-    mediumvioletred: '#c71585',
-    midnightblue: '#191970',
-    mintcream: '#f5fffa',
-    mistyrose: '#ffe4e1',
-    moccasin: '#ffe4b5',
-    navajowhite: '#ffdead',
-    navy: '#000080',
-    oldlace: '#fdf5e6',
-    olive: '#808000',
-    olivedrab: '#6b8e23',
-    orange: '#ffa500',
-    orangered: '#ff4500',
-    orchid: '#da70d6',
-    palegoldenrod: '#eee8aa',
-    palegreen: '#98fb98',
-    paleturquoise: '#afeeee',
-    palevioletred: '#db7093',
-    papayawhip: '#ffefd5',
-    peachpuff: '#ffdab9',
-    peru: '#cd853f',
-    pink: '#ffc0cb',
-    plum: '#dda0dd',
-    powderblue: '#b0e0e6',
-    purple: '#800080',
-    rebeccapurple: '#663399',
-    red: '#f00',
-    rosybrown: '#bc8f8f',
-    royalblue: '#4169e1',
-    saddlebrown: '#8b4513',
-    salmon: '#fa8072',
-    sandybrown: '#f4a460',
-    seagreen: '#2e8b57',
-    seashell: '#fff5ee',
-    sienna: '#a0522d',
-    silver: '#c0c0c0',
-    skyblue: '#87ceeb',
-    slateblue: '#6a5acd',
-    slategray: '#708090',
-    slategrey: '#708090',
-    snow: '#fffafa',
-    springgreen: '#00ff7f',
-    steelblue: '#4682b4',
-    tan: '#d2b48c',
-    teal: '#008080',
-    thistle: '#d8bfd8',
-    tomato: '#ff6347',
-    turquoise: '#40e0d0',
-    violet: '#ee82ee',
-    wheat: '#f5deb3',
-    white: '#fff',
-    whitesmoke: '#f5f5f5',
-    yellow: '#ff0',
-    yellowgreen: '#9acd32'
-  };
-
-  var toHex = {};
-  var toName = {};
-
-  for (var name in COLORS) {
-    var color = COLORS[name];
-    if (name.length < color.length)
-      toName[color] = name;
-    else
-      toHex[name] = color;
-  }
-
-  return {
-    toHex: toHex,
-    toName: toName,
-
-    // replace color name with hex values if shorter (or the other way around)
-    process: function() {
-      [toHex, toName].forEach(function(conversion) {
-        var pattern = '(' + Object.keys(conversion).join('|') + ')';
-        var colorSwitcher = function(match, prefix, colorValue, suffix) {
-          return prefix + conversion[colorValue.toLowerCase()] + suffix;
-        };
-        data = data.replace(new RegExp('([ :,\\(])' + pattern + '([;\\}!\\) ])', 'ig'), colorSwitcher);
-        data = data.replace(new RegExp('(,)' + pattern + '(,)', 'ig'), colorSwitcher);
-      });
-
-      return data;
-    }
-  };
-};
index bfc31c8..3e0b6b4 100644 (file)
@@ -18,10 +18,16 @@ function rebuild(tokens, keepBreaks) {
       if (token.body.length === 0 || (token.body.length == 1 && token.body[0] === ''))
         return '';
 
-      return token.block ?
-        token.block + '{' + rebuild(token.body, keepBreaks) + '}' :
-        token.selector.join(',') + '{' + token.body.join(';') + '}';
+      if (token.block) {
+        var body = rebuild(token.body, keepBreaks, token.isFlatBlock);
+        return body.length > 0 ?
+          token.block + '{' + body + '}' :
+          '';
+      } else {
+        return token.selector.join(',') + '{' + token.body.join(';') + '}';
+      }
     })
+    .filter(function (value) { return value.length > 0; })
     .join(keepBreaks ? lineBreak : '')
     .trim();
 }
index 5ca68f7..6b7e59f 100644 (file)
 var PropertyOptimizer = require('../../properties/optimizer');
 var CleanUp = require('./clean-up');
+var Splitter = require('../../utils/splitter');
+
+var RGB = require('../../colors/rgb');
+var HSL = require('../../colors/hsl');
+var HexNameShortener = require('../../colors/hex-name-shortener');
+
+var DEFAULT_ROUNDING_PRECISION = 2;
+var CHARSET_TOKEN = '@charset';
+var CHARSET_REGEXP = new RegExp('^' + CHARSET_TOKEN, 'i');
 
 function SimpleOptimizer(options, context) {
   this.options = options;
   this.propertyOptimizer = new PropertyOptimizer(this.options.compatibility, this.options.aggressiveMerging, context);
 }
 
-function minify(tokens) {
-  for (var i = 0, l = tokens.length; i < l; i++) {
-    var token = tokens[i];
+function removeUnsupported(token, compatibility) {
+  if (compatibility == 'ie7')
+    return;
+
+  var supported = [];
+  for (var i = 0, l = token.selector.length; i < l; i++) {
+    var selector = token.selector[i];
+
+    if (selector.indexOf('*+html ') === -1 && selector.indexOf('*:first-child+html ') === -1)
+      supported.push(selector);
+  }
+
+  token.selector = supported;
+}
+
+var valueMinifiers = {
+  'background': function (value) {
+    return value == 'none' || value == 'transparent' ? '0 0' : value;
+  },
+  'border-*-radius': function (value) {
+    if (value.indexOf('/') == -1)
+      return value;
 
-    if (token.selector) {
-      token.selector = CleanUp.selectors(token.selector);
-    } else if (token.block) {
-      token.block = CleanUp.block(token.block);
-      minify(token.body);
+    var parts = value.split(/\s*\/\s*/);
+    if (parts[0] == parts[1])
+      return parts[0];
+    else
+      return parts[0] + '/' + parts[1];
+  },
+  'filter': function (value) {
+    if (value.indexOf('DXImageTransform') === value.lastIndexOf('DXImageTransform')) {
+      value = value.replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)/, function (match, filter) {
+        return filter.toLowerCase();
+      });
     }
+
+    return value
+      .replace(/,(\S)/g, ', $1')
+      .replace(/ ?= ?/g, '=');
+  },
+  'font': function (value) {
+    var parts = value.split(' ');
+
+    if (parts[1] != 'normal' && parts[1] != 'bold' && !/^[1-9]00/.test(parts[1]))
+      parts[0] = this['font-weight'](parts[0]);
+
+    return parts.join(' ');
+  },
+  'font-weight': function (value) {
+    if (value == 'normal')
+      return '400';
+    else if (value == 'bold')
+      return '700';
+    else
+      return value;
+  },
+  'outline': function (value) {
+    return value == 'none' ? '0' : value;
   }
+};
+
+function zeroMinifier(_, value) {
+  return value
+    .replace(/\-0$/g, '0')
+    .replace(/\-0([^\.])/g, '0$1')
+    .replace(/(^|\s)0+([1-9])/g, '$1$2')
+    .replace(/(^|\D)\.0+(\D|$)/g, '$10$2')
+    .replace(/(^|\D)\.0+(\D|$)/g, '$10$2')
+    .replace(/\.([1-9]*)0+(\D|$)/g, function(match, nonZeroPart, suffix) {
+      return (nonZeroPart.length > 0 ? '.' : '') + nonZeroPart + suffix;
+    })
+    .replace(/(^|\D)0\.(\d)/g, '$1.$2');
+}
+
+function precisionMinifier(_, value, precision) {
+  if (precision === undefined)
+    precision = DEFAULT_ROUNDING_PRECISION;
+  var decimalMultiplier = Math.pow(10, precision);
+
+  return value
+    .replace(new RegExp('\\.(\\d{' + (precision + 1) + ',})px', 'g'), function(match, decimalPlaces) {
+      var newFraction = Math.round(parseFloat('.' + decimalPlaces) * decimalMultiplier) / decimalMultiplier;
+      return precision === 0 || newFraction === 0 ?
+        'px' :
+        '.' + ('' + newFraction).substring('0.'.length) + 'px';
+    })
+    .replace(/(\d)\.($|\D)/g, '$1$2');
+}
+
+function unitMinifier(_, value, compatibility) {
+  var units = ['px', 'em', 'ex', 'cm', 'mm', 'in', 'pt', 'pc', '%'];
+  if (['ie7', 'ie8'].indexOf(compatibility) == -1)
+    units.push('rem');
+
+  return value.replace(new RegExp('(^|\\s|\\(|,)0(?:' + units.join('|') + ')', 'g'), '$1' + '0');
+}
+
+function multipleZerosMinifier(property, value) {
+  if (property.indexOf('box-shadow') > -1)
+    return value == '0 0 0 0' ? '0 0' : value;
+
+  return value.replace(/^0 0 0 0$/, '0');
+}
+
+function colorMininifier(property, value, compatibility) {
+  value = value
+    .replace(/rgb\((\-?\d+),(\-?\d+),(\-?\d+)\)/g, function (match, red, green, blue) {
+      return new RGB(red, green, blue).toHex();
+    })
+    .replace(/hsl\((-?\d+),(-?\d+)%?,(-?\d+)%?\)/g, function (match, hue, saturation, lightness) {
+      return new HSL(hue, saturation, lightness).toHex();
+    })
+    .replace(/(^|[^='"])#([0-9a-f]{6})/gi, function (match, prefix, color) {
+      if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5])
+        return prefix + '#' + color[0] + color[2] + color[4];
+      else
+        return prefix + '#' + color;
+    })
+    .replace(/(rgb|rgba|hsl|hsla)\(([^\)]+)\)/g, function(match, colorFunction, colorDef) {
+      var tokens = colorDef.split(',');
+      var applies = colorFunction == 'hsl' || colorFunction == 'hsla' || tokens[0].indexOf('%') > -1;
+      if (!applies)
+        return match;
+
+      if (tokens[1].indexOf('%') == -1)
+        tokens[1] += '%';
+      if (tokens[2].indexOf('%') == -1)
+        tokens[2] += '%';
+      return colorFunction + '(' + tokens.join(',') + ')';
+    });
+
+  if (!compatibility) {
+    value = value.replace(/(?:rgba|hsla)\(0,0%?,0%?,0\)/g, function (match) {
+      if (new Splitter(',').split(value).pop().indexOf('gradient(') > -1)
+        return match;
+
+      return 'transparent';
+    });
+  }
+
+  return new HexNameShortener(value).shorten();
+}
+
+function reduce(body, options) {
+  return body.map(function (token) {
+    var firstColon = token.indexOf(':');
+    var property = token.substring(0, firstColon);
+    var value = token.substring(firstColon + 1);
+    var important = false;
+
+    if (!options.compatibility && (property[0] == '_' || property[0] == '*'))
+      return '';
+
+    if (value.indexOf('!important') > 0 || value.indexOf('! important') > 0) {
+      value = value.substring(0, value.indexOf('!')).trim();
+      important = true;
+    }
+
+    if (property.indexOf('border') === 0 && property.indexOf('radius') > 0)
+      value = valueMinifiers['border-*-radius'](value);
+
+    if (valueMinifiers[property])
+      value = valueMinifiers[property](value);
+
+    value = zeroMinifier(property, value);
+    value = precisionMinifier(property, value, options.roundingPrecision);
+    value = unitMinifier(property, value, options.compatibility);
+    value = multipleZerosMinifier(property, value);
+    value = colorMininifier(property, value, options.compatibility);
+
+    return property + ':' + value + (important ? '!important' : '');
+  });
 }
 
 SimpleOptimizer.prototype.optimize = function(tokens) {
-  minify(tokens);
+  var self = this;
+  var hasCharset = false;
+
+  function _optimize(tokens) {
+    for (var i = 0, l = tokens.length; i < l; i++) {
+      var token = tokens[i];
+      // FIXME: why it's so?
+      if (!token)
+        break;
+
+      if (token.selector) {
+        token.selector = CleanUp.selectors(token.selector);
+
+        removeUnsupported(token, self.options.compatibility);
+        if (token.selector.length === 0) {
+          tokens.splice(i, 1);
+          i--;
+          continue;
+        }
+
+        token.body = reduce(token.body, self.options);
+      } else if (token.block) {
+        token.block = CleanUp.block(token.block);
+        if (token.isFlatBlock)
+          token.body = reduce(token.body, self.options);
+        else
+          _optimize(token.body);
+      } else if (typeof token === 'string') {
+        if (CHARSET_REGEXP.test(token)) {
+          if (hasCharset || token.indexOf(CHARSET_TOKEN) == -1) {
+            tokens.splice(i, 1);
+            i++;
+          } else {
+            hasCharset = true;
+            tokens.splice(i, 1);
+            tokens.unshift(token.replace(CHARSET_REGEXP, CHARSET_TOKEN));
+          }
+        }
+      }
+    }
+  }
+
+  _optimize(tokens);
 };
 
 module.exports = SimpleOptimizer;
diff --git a/test/colors/hex-name-shortener-test.js b/test/colors/hex-name-shortener-test.js
new file mode 100644 (file)
index 0000000..4060bd3
--- /dev/null
@@ -0,0 +1,191 @@
+var vows = require('vows');
+var assert = require('assert');
+var HexNameShortener = require('../../lib/colors/hex-name-shortener');
+
+var COLORS = {
+  aliceblue: '#f0f8ff',
+  antiquewhite: '#faebd7',
+  aqua: '#0ff',
+  aquamarine: '#7fffd4',
+  azure: '#f0ffff',
+  beige: '#f5f5dc',
+  bisque: '#ffe4c4',
+  black: '#000',
+  blanchedalmond: '#ffebcd',
+  blue: '#00f',
+  blueviolet: '#8a2be2',
+  brown: '#a52a2a',
+  burlywood: '#deb887',
+  cadetblue: '#5f9ea0',
+  chartreuse: '#7fff00',
+  chocolate: '#d2691e',
+  coral: '#ff7f50',
+  cornflowerblue: '#6495ed',
+  cornsilk: '#fff8dc',
+  crimson: '#dc143c',
+  cyan: '#0ff',
+  darkblue: '#00008b',
+  darkcyan: '#008b8b',
+  darkgoldenrod: '#b8860b',
+  darkgray: '#a9a9a9',
+  darkgreen: '#006400',
+  darkgrey: '#a9a9a9',
+  darkkhaki: '#bdb76b',
+  darkmagenta: '#8b008b',
+  darkolivegreen: '#556b2f',
+  darkorange: '#ff8c00',
+  darkorchid: '#9932cc',
+  darkred: '#8b0000',
+  darksalmon: '#e9967a',
+  darkseagreen: '#8fbc8f',
+  darkslateblue: '#483d8b',
+  darkslategray: '#2f4f4f',
+  darkslategrey: '#2f4f4f',
+  darkturquoise: '#00ced1',
+  darkviolet: '#9400d3',
+  deeppink: '#ff1493',
+  deepskyblue: '#00bfff',
+  dimgray: '#696969',
+  dimgrey: '#696969',
+  dodgerblue: '#1e90ff',
+  firebrick: '#b22222',
+  floralwhite: '#fffaf0',
+  forestgreen: '#228b22',
+  fuchsia: '#f0f',
+  gainsboro: '#dcdcdc',
+  ghostwhite: '#f8f8ff',
+  gold: '#ffd700',
+  goldenrod: '#daa520',
+  gray: '#808080',
+  green: '#008000',
+  greenyellow: '#adff2f',
+  grey: '#808080',
+  honeydew: '#f0fff0',
+  hotpink: '#ff69b4',
+  indianred: '#cd5c5c',
+  indigo: '#4b0082',
+  ivory: '#fffff0',
+  khaki: '#f0e68c',
+  lavender: '#e6e6fa',
+  lavenderblush: '#fff0f5',
+  lawngreen: '#7cfc00',
+  lemonchiffon: '#fffacd',
+  lightblue: '#add8e6',
+  lightcoral: '#f08080',
+  lightcyan: '#e0ffff',
+  lightgoldenrodyellow: '#fafad2',
+  lightgray: '#d3d3d3',
+  lightgreen: '#90ee90',
+  lightgrey: '#d3d3d3',
+  lightpink: '#ffb6c1',
+  lightsalmon: '#ffa07a',
+  lightseagreen: '#20b2aa',
+  lightskyblue: '#87cefa',
+  lightslategray: '#778899',
+  lightslategrey: '#778899',
+  lightsteelblue: '#b0c4de',
+  lightyellow: '#ffffe0',
+  lime: '#0f0',
+  limegreen: '#32cd32',
+  linen: '#faf0e6',
+  magenta: '#ff00ff',
+  maroon: '#800000',
+  mediumaquamarine: '#66cdaa',
+  mediumblue: '#0000cd',
+  mediumorchid: '#ba55d3',
+  mediumpurple: '#9370db',
+  mediumseagreen: '#3cb371',
+  mediumslateblue: '#7b68ee',
+  mediumspringgreen: '#00fa9a',
+  mediumturquoise: '#48d1cc',
+  mediumvioletred: '#c71585',
+  midnightblue: '#191970',
+  mintcream: '#f5fffa',
+  mistyrose: '#ffe4e1',
+  moccasin: '#ffe4b5',
+  navajowhite: '#ffdead',
+  navy: '#000080',
+  oldlace: '#fdf5e6',
+  olive: '#808000',
+  olivedrab: '#6b8e23',
+  orange: '#ffa500',
+  orangered: '#ff4500',
+  orchid: '#da70d6',
+  palegoldenrod: '#eee8aa',
+  palegreen: '#98fb98',
+  paleturquoise: '#afeeee',
+  palevioletred: '#db7093',
+  papayawhip: '#ffefd5',
+  peachpuff: '#ffdab9',
+  peru: '#cd853f',
+  pink: '#ffc0cb',
+  plum: '#dda0dd',
+  powderblue: '#b0e0e6',
+  purple: '#800080',
+  rebeccapurple: '#663399',
+  red: '#f00',
+  rosybrown: '#bc8f8f',
+  royalblue: '#4169e1',
+  saddlebrown: '#8b4513',
+  salmon: '#fa8072',
+  sandybrown: '#f4a460',
+  seagreen: '#2e8b57',
+  seashell: '#fff5ee',
+  sienna: '#a0522d',
+  silver: '#c0c0c0',
+  skyblue: '#87ceeb',
+  slateblue: '#6a5acd',
+  slategray: '#708090',
+  slategrey: '#708090',
+  snow: '#fffafa',
+  springgreen: '#00ff7f',
+  steelblue: '#4682b4',
+  tan: '#d2b48c',
+  teal: '#008080',
+  thistle: '#d8bfd8',
+  tomato: '#ff6347',
+  turquoise: '#40e0d0',
+  violet: '#ee82ee',
+  wheat: '#f5deb3',
+  white: '#fff',
+  whitesmoke: '#f5f5f5',
+  yellow: '#ff0',
+  yellowgreen: '#9acd32'
+};
+
+function colorShorteningContext() {
+  var context = {};
+
+  function shortened(target) {
+    return function (source) {
+      assert.equal(new HexNameShortener(source).shorten(), target);
+    };
+  }
+
+  for (var name in COLORS) {
+    var hex = COLORS[name];
+    var from, to;
+
+    if (hex == COLORS.gray)
+      name = 'grey';
+
+    if (hex.length <= name.length) {
+      from = name;
+      to = hex;
+    } else {
+      from = hex;
+      to = name;
+    }
+
+    context['should turn \'' + from + '\' into \'' + to + '\''] = {
+      topic: from,
+      shortened: shortened(to)
+    };
+  }
+
+  return context;
+}
+
+vows.describe(HexNameShortener)
+  .addBatch(colorShorteningContext())
+  .export(module);
index c65f356..2c9c7da 100644 (file)
@@ -4,7 +4,6 @@ var vows = require('vows');
 var assert = require('assert');
 var path = require('path');
 var CleanCSS = require('../index');
-var ColorShortener = require('../lib/colors/shortener');
 
 var lineBreak = process.platform == 'win32' ? '\r\n' : '\n';
 var cssContext = function(groups, options) {
@@ -30,23 +29,6 @@ var cssContext = function(groups, options) {
   return context;
 };
 
-var colorShorteningContext = function() {
-  var shortenerContext = {};
-  var shortener = new ColorShortener();
-
-  ['toName', 'toHex'].forEach(function(type) {
-    for (var from in shortener[type]) {
-      var to = shortener[type][from];
-      shortenerContext['should turn ' + from + ' into ' + to] = [
-        'a{color:' + from + '}',
-        'a{color:' + to + '}'
-      ];
-    }
-  });
-
-  return cssContext(shortenerContext);
-};
-
 var redefineContext = function(redefinitions, options) {
   var context = {};
   var vendorPrefixes = ['', '-moz-', '-o-', '-webkit-']; // there is no -ms-animation nor -ms-transition.
@@ -850,7 +832,6 @@ vows.describe('integration tests').addBatch({
       'a{border-radius:5px}'
     ]
   }),
-  'shortening colors': colorShorteningContext(),
   'font weights': cssContext({
     'font-weight:normal to 400': [
       'p{font-weight:normal}',
index 18c2560..b37c042 100644 (file)
@@ -130,11 +130,11 @@ vows.describe(SelectorsOptimizer)
       ],
       'duplicates - same context': [
         'a{color:red}div{color:blue}a{color:red}',
-        'div{color:blue}a{color:red}'
+        'div{color:#00f}a{color:red}'
       ],
       'duplicates - different contexts': [
         'a{color:red}div{color:blue}@media screen{a{color:red}}',
-        'a{color:red}div{color:blue}@media screen{a{color:red}}'
+        'a{color:red}div{color:#00f}@media screen{a{color:red}}'
       ],
       'adjacent': [
         'a{color:red}a{display:block;width:100px}div{color:#fff}',
@@ -170,11 +170,11 @@ vows.describe(SelectorsOptimizer)
       ],
       'duplicates - same context': [
         'a{color:red}div{color:blue}a{color:red}',
-        'a{color:red}div{color:blue}a{color:red}'
+        'a{color:red}div{color:#00f}a{color:red}'
       ],
       'duplicates - different contexts': [
         'a{color:red}div{color:blue}@media screen{a{color:red}}',
-        'a{color:red}div{color:blue}@media screen{a{color:red}}'
+        'a{color:red}div{color:#00f}@media screen{a{color:red}}'
       ],
       'adjacent': [
         'a{color:red}a{display:block;width:100px}div{color:#fff}',
@@ -186,4 +186,28 @@ vows.describe(SelectorsOptimizer)
       ]
     }, { noAdvanced: true })
   )
+  .addBatch(
+    optimizerContext('@charset', {
+      'multiple': [
+        '@charset \'utf-8\';a{color:red}@charset \'utf-8\';',
+        '@charset \'utf-8\';a{color:red}'
+      ],
+      'not at beginning': [
+        'a{color:red}@charset \'utf-8\';',
+        '@charset \'utf-8\';a{color:red}'
+      ],
+      'different case': [
+        'a{color:red}@ChArSeT \'utf-8\';',
+        'a{color:red}'
+      ]
+    })
+  )
+  .addBatch(
+    optimizerContext('@font-face', {
+      'rebuilding': [
+        '@font-face{font-family:PublicVintage;src:url(/PublicVintage.otf) format(\'opentype\')}',
+        '@font-face{font-family:PublicVintage;src:url(/PublicVintage.otf) format(\'opentype\')}'
+      ]
+    })
+  )
   .export(module);
index de1717b..e5d167a 100644 (file)
@@ -4,20 +4,44 @@ var assert = require('assert');
 var Tokenizer = require('../../../lib/selectors/tokenizer');
 var SimpleOptimizer = require('../../../lib/selectors/optimizers/simple');
 
-function selectorContext(specs) {
+function selectorContext(group, specs, options) {
   var context = {};
+  options = options || {};
 
   function optimized(selectors) {
     return function (source) {
       var tokens = new Tokenizer().toTokens(source);
-      new SimpleOptimizer({}).optimize(tokens);
+      new SimpleOptimizer(options).optimize(tokens);
 
-      assert.deepEqual(tokens[0].selector, selectors);
+      assert.deepEqual(tokens[0] ? tokens[0].selector : null, selectors);
     };
   }
 
   for (var name in specs) {
-    context['selector - ' + name] = {
+    context['selector - ' + group + ' - ' + name] = {
+      topic: specs[name][0],
+      optimized: optimized(specs[name][1])
+    };
+  }
+
+  return context;
+}
+
+function propertyContext(group, specs, options) {
+  var context = {};
+  options = options || {};
+
+  function optimized(selectors) {
+    return function (source) {
+      var tokens = new Tokenizer().toTokens(source);
+      new SimpleOptimizer(options).optimize(tokens);
+
+      assert.deepEqual(tokens[0].body, selectors);
+    };
+  }
+
+  for (var name in specs) {
+    context['property - ' + group + ' - ' + name] = {
       topic: specs[name][0],
       optimized: optimized(specs[name][1])
     };
@@ -28,7 +52,7 @@ function selectorContext(specs) {
 
 vows.describe(SimpleOptimizer)
   .addBatch(
-    selectorContext({
+    selectorContext('default', {
       'optimized': [
         'a{}',
         ['a']
@@ -40,7 +64,367 @@ vows.describe(SimpleOptimizer)
       'line breaks': [
         ' div  >\n\r\n span{}',
         ['div>span']
+      ],
+      '+html': [
+        '*+html .foo{display:inline}',
+        null
       ]
     })
   )
+  .addBatch(
+    selectorContext('ie8', {
+      '+html': [
+        '*+html .foo{display:inline}',
+        null
+      ],
+      '+first-child html': [
+        '*:first-child+html .foo{display:inline}',
+        null
+      ],
+      '+html - complex': [
+        '*+html .foo,.bar{display:inline}',
+        ['.bar']
+      ]
+    }, { compatibility: 'ie8' })
+  )
+  .addBatch(
+    selectorContext('ie7', {
+      '+html': [
+        '*+html .foo{display:inline}',
+        ['*+html .foo']
+      ],
+      '+html - complex': [
+        '*+html .foo,.bar{display:inline}',
+        ['*+html .foo', '.bar']
+      ]
+    }, { compatibility: 'ie7' })
+  )
+  .addBatch(
+    propertyContext('@background', {
+      'none to 0 0': [
+        'a{background:none}',
+        ['background:0 0']
+      ],
+      'transparent to 0 0': [
+        'a{background:transparent}',
+        ['background:0 0']
+      ],
+      'any other': [
+        'a{background:red}',
+        ['background:red']
+      ]
+    })
+  )
+  .addBatch(
+    propertyContext('@border-*-radius', {
+      'spaces around /': [
+        'a{border-top-left-radius:2em  /  1em}',
+        ['border-top-left-radius:2em/1em']
+      ],
+      'symmetric expanded to shorthand': [
+        'a{border-top-left-radius:1em 2em 3em 4em / 1em 2em 3em 4em}',
+        ['border-top-left-radius:1em 2em 3em 4em']
+      ]
+    })
+  )
+  .addBatch(
+    propertyContext('@box-shadow', {
+      'four zeros': [
+        'a{box-shadow:0 0 0 0}',
+        ['box-shadow:0 0']
+      ],
+      'four zeros in vendor prefixed': [
+        'a{-webkit-box-shadow:0 0 0 0}',
+        ['-webkit-box-shadow:0 0']
+      ]
+    })
+  )
+  .addBatch(
+    propertyContext('colors', {
+      'rgb to hex': [
+        'a{color:rgb(255,254,253)}',
+        ['color:#fffefd']
+      ],
+      'rgba not to hex': [
+        'a{color:rgba(255,254,253,.5)}',
+        ['color:rgba(255,254,253,.5)']
+      ],
+      'hsl to hex': [
+        'a{color:hsl(240,100%,50%)}',
+        ['color:#00f']
+      ],
+      'hsla not to hex': [
+        'a{color:hsla(240,100%,50%,.5)}',
+        ['color:hsla(240,100%,50%,.5)']
+      ],
+      'long hex to short hex': [
+        'a{color:#ff00ff}',
+        ['color:#f0f']
+      ],
+      'hex to name': [
+        'a{color:#f00}',
+        ['color:red']
+      ],
+      'name to hex': [
+        'a{color:white}',
+        ['color:#fff']
+      ],
+      'transparent black rgba to transparent': [
+        'a{color:rgba(0,0,0,0)}',
+        ['color:transparent']
+      ],
+      'transparent non-black rgba': [
+        'a{color:rgba(255,0,0,0)}',
+        ['color:rgba(255,0,0,0)']
+      ],
+      'transparent black hsla to transparent': [
+        'a{color:hsla(0,0%,0%,0)}',
+        ['color:transparent']
+      ],
+      'transparent non-black hsla': [
+        'a{color:rgba(240,0,0,0)}',
+        ['color:rgba(240,0,0,0)']
+      ]
+    })
+  )
+  .addBatch(
+    propertyContext('colors - ie8 compatibility', {
+      'transparent black rgba': [
+        'a{color:rgba(0,0,0,0)}',
+        ['color:rgba(0,0,0,0)']
+      ],
+      'transparent non-black rgba': [
+        'a{color:rgba(255,0,0,0)}',
+        ['color:rgba(255,0,0,0)']
+      ],
+      'transparent black hsla': [
+        'a{color:hsla(0,0%,0%,0)}',
+        ['color:hsla(0,0%,0%,0)']
+      ],
+      'transparent non-black hsla': [
+        'a{color:rgba(240,0,0,0)}',
+        ['color:rgba(240,0,0,0)']
+      ]
+    }, { compatibility: 'ie8' })
+  )
+  .addBatch(
+    propertyContext('@filter', {
+      'spaces after comma': [
+        'a{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#cccccc\',endColorstr=\'#000000\', enabled=true)}',
+        ['filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#cccccc\', endColorstr=\'#000000\', enabled=true)']
+      ],
+      'single Alpha filter': [
+        'a{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80)}',
+        ['filter:alpha(Opacity=80)']
+      ],
+      'single Chroma filter': [
+        'a{filter:progid:DXImageTransform.Microsoft.Chroma(color=#919191)}',
+        ['filter:chroma(color=#919191)']
+      ],
+      'multiple filters': [
+        'a{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80) progid:DXImageTransform.Microsoft.Chroma(color=#919191)}',
+        ['filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80) progid:DXImageTransform.Microsoft.Chroma(color=#919191)']
+      ]
+    })
+  )
+  .addBatch(
+    propertyContext('@font', {
+      'in shorthand': [
+        'a{font:normal 13px/20px sans-serif}',
+        ['font:400 13px/20px sans-serif']
+      ],
+      'in shorthand with fractions': [
+        'a{font:bold .9em sans-serif}',
+        ['font:700 .9em sans-serif']
+      ],
+      'with font wariant and style': [
+        'a{font:normal normal normal 13px/20px sans-serif}',
+        ['font:normal normal normal 13px/20px sans-serif']
+      ],
+      'with mixed order of variant and style': [
+        'a{font:normal 300 normal 13px/20px sans-serif}',
+        ['font:normal 300 normal 13px/20px sans-serif']
+      ]
+    })
+  )
+  .addBatch(
+    propertyContext('@font-weight', {
+      'normal to 400': [
+        'a{font-weight:normal}',
+        ['font-weight:400']
+      ],
+      'bold to 700': [
+        'a{font-weight:bold}',
+        ['font-weight:700']
+      ],
+      'any other': [
+        'a{font-weight:bolder}',
+        ['font-weight:bolder']
+      ]
+    })
+  )
+  .addBatch(
+    propertyContext('ie hacks', {
+      'underscore': [
+        'a{_width:100px}',
+        ['']
+      ],
+      'star': [
+        'a{*width:100px}',
+        ['']
+      ]
+    })
+  )
+  .addBatch(
+    propertyContext('ie hacks in compatibility mode', {
+      'underscore': [
+        'a{_width:100px}',
+        ['_width:100px']
+      ],
+      'star': [
+        'a{*width:100px}',
+        ['*width:100px']
+      ]
+    }, { compatibility: 'ie8' })
+  )
+  .addBatch(
+    propertyContext('important', {
+      'minified': [
+        'a{color:red!important}',
+        ['color:red!important']
+      ],
+      'space before !': [
+        'a{color:red !important}',
+        ['color:red!important']
+      ],
+      'space after !': [
+        'a{color:red! important}',
+        ['color:red!important']
+      ]
+    }, { compatibility: 'ie8' })
+  )
+  .addBatch(
+    propertyContext('@outline', {
+      'none to 0': [
+        'a{outline:none}',
+        ['outline:0']
+      ],
+      'any other': [
+        'a{outline:10px}',
+        ['outline:10px']
+      ]
+    })
+  )
+  .addBatch(
+    propertyContext('rounding', {
+      'pixels': [
+        'a{transform:translateY(123.31135px)}',
+        ['transform:translateY(123.311px)']
+      ],
+      'percents': [
+        'a{left:20.1231%}',
+        ['left:20.1231%']
+      ],
+      'ems': [
+        'a{left:1.1231em}',
+        ['left:1.1231em']
+      ]
+    }, { roundingPrecision: 3 })
+  )
+  .addBatch(
+    propertyContext('units', {
+      'pixels': [
+        'a{width:0px}',
+        ['width:0']
+      ],
+      'mixed units': [
+        'a{margin:0em 0rem 0px 0pt}',
+        ['margin:0']
+      ],
+      'mixed vales': [
+        'a{padding:10px 0em 30% 0rem}',
+        ['padding:10px 0 30% 0']
+      ]
+    })
+  )
+  .addBatch(
+    propertyContext('units in compatibility mode', {
+      'pixels': [
+        'a{width:0px}',
+        ['width:0']
+      ],
+      'mixed units': [
+        'a{margin:0em 0rem 0px 0pt}',
+        ['margin:0 0rem 0 0']
+      ],
+      'mixed vales': [
+        'a{padding:10px 0em 30% 0rem}',
+        ['padding:10px 0 30% 0rem']
+      ]
+    }, { compatibility: 'ie8' })
+  )
+  .addBatch(
+    propertyContext('zeros', {
+      '-0 to 0': [
+        'a{margin:-0}',
+        ['margin:0']
+      ],
+      '-0px to 0': [
+        'a{margin:-0px}',
+        ['margin:0']
+      ],
+      'missing': [
+        'a{opacity:1.}',
+        ['opacity:1']
+      ],
+      'multiple': [
+        'a{margin:-0 -0 -0 -0}',
+        ['margin:0']
+      ],
+      'keeps negative non-zero': [
+        'a{margin:-0.5em}',
+        ['margin:-.5em']
+      ],
+      'strips leading from value': [
+        'a{padding:010px 0015px}',
+        ['padding:10px 15px']
+      ],
+      'strips leading from fractions': [
+        'a{margin:-0.5em}',
+        ['margin:-.5em']
+      ],
+      'strips trailing from opacity': [
+        'a{opacity:1.0}',
+        ['opacity:1']
+      ],
+      '.0 to 0': [
+        'a{margin:.0 .0 .0 .0}',
+        ['margin:0']
+      ],
+      'fraction zeros': [
+        'a{margin:10.0em 15.50em 10.01em 0.0em}',
+        ['margin:10em 15.5em 10.01em 0']
+      ],
+      'fraction zeros after rounding': [
+        'a{margin:10.0010px}',
+        ['margin:10px']
+      ],
+      'four zeros into one': [
+        'a{margin:0 0 0 0}',
+        ['margin:0']
+      ],
+      'rect zeros': [
+        'a{clip:rect(0px 0px 0px 0px)}',
+        ['clip:rect(0 0 0 0)']
+      ],
+      'rect zeros with non-zero value': [
+        'a{clip:rect(0.5% 0px  0px 0px)}',
+        ['clip:rect(.5% 0 0 0)']
+      ],
+      'rect zeros with commas': [
+        'a{clip:rect(0px, 0px, 0px, 0px)}',
+        ['clip:rect(0,0,0,0)']
+      ],
+    })
+  )
   .export(module);