From c95c950eb6c281c8438a55c430251b70b10874df Mon Sep 17 00:00:00 2001 From: Jakub Pawlowicz Date: Wed, 1 Oct 2014 10:59:35 +0100 Subject: [PATCH] Adds value & charset optimizations. * Re-implemented most of property values' minifications. * Extra charset(s) are stripped and first one is moved to the beginning. --- lib/colors/hex-name-shortener.js | 181 +++++++++++ lib/colors/hsl-to-hex.js | 64 ---- lib/colors/hsl.js | 67 ++++ lib/colors/long-to-short-hex.js | 12 - lib/colors/rgb-to-hex.js | 14 - lib/colors/rgb.js | 16 + lib/colors/shortener.js | 182 ----------- lib/selectors/optimizer.js | 12 +- lib/selectors/optimizers/simple.js | 230 ++++++++++++- test/colors/hex-name-shortener-test.js | 191 +++++++++++ test/integration-test.js | 19 -- test/selectors/optimizer-test.js | 32 +- test/selectors/optimizers/simple-test.js | 394 ++++++++++++++++++++++- 13 files changed, 1102 insertions(+), 312 deletions(-) create mode 100644 lib/colors/hex-name-shortener.js delete mode 100644 lib/colors/hsl-to-hex.js create mode 100644 lib/colors/hsl.js delete mode 100644 lib/colors/long-to-short-hex.js delete mode 100644 lib/colors/rgb-to-hex.js create mode 100644 lib/colors/rgb.js delete mode 100644 lib/colors/shortener.js create mode 100644 test/colors/hex-name-shortener-test.js diff --git a/lib/colors/hex-name-shortener.js b/lib/colors/hex-name-shortener.js new file mode 100644 index 00000000..914b2356 --- /dev/null +++ b/lib/colors/hex-name-shortener.js @@ -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 index 6bb50312..00000000 --- a/lib/colors/hsl-to-hex.js +++ /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 index 00000000..5c76b6e6 --- /dev/null +++ b/lib/colors/hsl.js @@ -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 index 87fa31b2..00000000 --- a/lib/colors/long-to-short-hex.js +++ /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 index acfbf011..00000000 --- a/lib/colors/rgb-to-hex.js +++ /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 index 00000000..2f945482 --- /dev/null +++ b/lib/colors/rgb.js @@ -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 index 337a727c..00000000 --- a/lib/colors/shortener.js +++ /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; - } - }; -}; diff --git a/lib/selectors/optimizer.js b/lib/selectors/optimizer.js index bfc31c8b..3e0b6b40 100644 --- a/lib/selectors/optimizer.js +++ b/lib/selectors/optimizer.js @@ -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(); } diff --git a/lib/selectors/optimizers/simple.js b/lib/selectors/optimizers/simple.js index 5ca68f71..6b7e59f0 100644 --- a/lib/selectors/optimizers/simple.js +++ b/lib/selectors/optimizers/simple.js @@ -1,26 +1,238 @@ 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 index 00000000..4060bd3d --- /dev/null +++ b/test/colors/hex-name-shortener-test.js @@ -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); diff --git a/test/integration-test.js b/test/integration-test.js index c65f3560..2c9c7da0 100644 --- a/test/integration-test.js +++ b/test/integration-test.js @@ -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}', diff --git a/test/selectors/optimizer-test.js b/test/selectors/optimizer-test.js index 18c25605..b37c0429 100644 --- a/test/selectors/optimizer-test.js +++ b/test/selectors/optimizer-test.js @@ -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); diff --git a/test/selectors/optimizers/simple-test.js b/test/selectors/optimizers/simple-test.js index de1717b3..e5d167af 100644 --- a/test/selectors/optimizers/simple-test.js +++ b/test/selectors/optimizers/simple-test.js @@ -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); -- 2.34.1