Reorganizes level 1 optimizations.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Mon, 9 Jan 2017 13:48:34 +0000 (14:48 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 10 Jan 2017 06:47:33 +0000 (07:47 +0100)
Why:

* Slightly more coherent now, will get more love in 4.1.

lib/optimizer/level-1/optimize.js

index fb42d51..bd7b2b8 100644 (file)
@@ -36,118 +36,48 @@ var QUOTED_PATTERN = /^('.*'|".*")$/;
 var QUOTED_BUT_SAFE_PATTERN = /^['"][a-zA-Z][a-zA-Z\d\-_]+['"]$/;
 var URL_PREFIX_PATTERN = /^url\(/i;
 
-var valueMinifiers = {
-  'background': function (value, index, total) {
-    return index === 0 && total == 1 && (value == 'none' || value == 'transparent') ? '0 0' : value;
-  },
-  'font-weight': function (value, _index, _total, options) {
-    if (!options.compatibility.properties.fontWeight) {
-      return value;
-    } else if (value == 'normal') {
-      return '400';
-    } else if (value == 'bold') {
-      return '700';
-    } else {
-      return value;
-    }
-  },
-  'outline': function (value, index, total) {
-    return index === 0 && total == 1 && value == 'none' ? '0' : value;
-  }
-};
-
 function isNegative(value) {
   return value && value[1][0] == '-' && parseFloat(value[1]) < 0;
 }
 
-function zeroMinifier(name, value) {
-  if (value.indexOf('0') == -1)
-    return value;
-
-  if (value.indexOf('-') > -1) {
-    value = value
-      .replace(/([^\w\d\-]|^)\-0([^\.]|$)/g, '$10$2')
-      .replace(/([^\w\d\-]|^)\-0([^\.]|$)/g, '$10$2');
-  }
-
-  return value
-    .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 isQuoted(value) {
+  return QUOTED_PATTERN.test(value);
 }
 
-function zeroDegMinifier(_, value) {
-  if (value.indexOf('0deg') == -1)
-    return value;
-
-  return value.replace(/\(0deg\)/g, '(0)');
+function isUrl(value) {
+  return URL_PREFIX_PATTERN.test(value);
 }
 
-function whitespaceMinifier(name, value) {
-  if (name.indexOf('filter') > -1 || value.indexOf(' ') == -1 || value.indexOf('expression') === 0)
-    return value;
-
-  if (value.indexOf(Marker.SINGLE_QUOTE) > -1 || value.indexOf(Marker.DOUBLE_QUOTE) > -1)
-    return value;
-
-  value = value.replace(/\s+/g, ' ');
-
-  if (value.indexOf('calc') > -1)
-    value = value.replace(/\) ?\/ ?/g, ')/ ');
-
+function normalizeUrl(value) {
   return value
-    .replace(/(\(;?)\s+/g, '$1')
-    .replace(/\s+(;?\))/g, '$1')
-    .replace(/, /g, ',');
+    .replace(URL_PREFIX_PATTERN, 'url(')
+    .replace(/\\?\n|\\?\r\n/g, '');
 }
 
-function precisionMinifier(_, value, precisionOptions) {
-  var optimizedValue = value.replace(/(\d)\.($|\D)/g, '$1$2');
+function optimizeBackground(property) {
+  var values = property.value;
 
-  if (!precisionOptions.matcher || value.indexOf('.') === -1) {
-    return optimizedValue;
+  if (values.length == 1 && values[0][1] == 'none') {
+    values[0][1] = '0 0';
   }
 
-  return optimizedValue
-    .replace(precisionOptions.matcher, function (match, integerPart, fractionPart, unit) {
-      var multiplier = precisionOptions.units[unit].multiplier;
-      var parsedInteger = parseInt(integerPart);
-      var integer = isNaN(parsedInteger) ? 0 : parsedInteger;
-      var fraction = parseFloat(fractionPart);
-
-      return Math.round((integer + fraction) * multiplier) / multiplier + unit;
-    });
-}
-
-function unitMinifier(name, value, unitsRegexp) {
-  if (/^(?:\-moz\-calc|\-webkit\-calc|calc)\(/.test(value))
-    return value;
-
-  if (name == 'flex' || name == '-ms-flex' || name == '-webkit-flex' || name == 'flex-basis' || name == '-webkit-flex-basis')
-    return value;
-
-  if (value.indexOf('%') > 0 && (name == 'height' || name == 'max-height'))
-    return value;
-
-  return value
-    .replace(unitsRegexp, '$1' + '0' + '$2')
-    .replace(unitsRegexp, '$1' + '0' + '$2');
+  if (values.length == 1 && values[0][1] == 'transparent') {
+    values[0][1] = '0 0';
+  }
 }
 
-function multipleZerosMinifier(property) {
+function optimizeBorderRadius(property) {
   var values = property.value;
   var spliceAt;
 
-  if (values.length == 4 && values[0][1] === '0' && values[1][1] === '0' && values[2][1] === '0' && values[3][1] === '0') {
-    if (property.name.indexOf('box-shadow') > -1) {
-      spliceAt = 2;
-    } else {
-      spliceAt = 1;
-    }
+  if (values.length == 3 && values[1][1] == '/' && values[0][1] == values[2][1]) {
+    spliceAt = 1;
+  } else if (values.length == 5 && values[2][1] == '/' && values[0][1] == values[3][1] && values[1][1] == values[4][1]) {
+    spliceAt = 2;
+  } else if (values.length == 7 && values[3][1] == '/' && values[0][1] == values[4][1] && values[1][1] == values[5][1] && values[2][1] == values[6][1]) {
+    spliceAt = 3;
+  } else if (values.length == 9 && values[4][1] == '/' && values[0][1] == values[5][1] && values[1][1] == values[6][1] && values[2][1] == values[7][1] && values[3][1] == values[8][1]) {
+    spliceAt = 4;
   }
 
   if (spliceAt) {
@@ -156,7 +86,7 @@ function multipleZerosMinifier(property) {
   }
 }
 
-function colorMininifier(name, value, compatibility) {
+function optimizeColors(name, value, compatibility) {
   if (value.indexOf('#') === -1 && value.indexOf('rgb') == -1 && value.indexOf('hsl') == -1) {
     return shortenHex(value);
   }
@@ -184,20 +114,27 @@ function colorMininifier(name, value, compatibility) {
         (colorFunction == 'hsla' && tokens.length == 4) ||
         (colorFunction == 'rgb' && tokens.length == 3 && colorDef.indexOf('%') > 0) ||
         (colorFunction == 'rgba' && tokens.length == 4 && colorDef.indexOf('%') > 0);
-      if (!applies)
+
+      if (!applies) {
         return match;
+      }
 
-      if (tokens[1].indexOf('%') == -1)
+      if (tokens[1].indexOf('%') == -1) {
         tokens[1] += '%';
-      if (tokens[2].indexOf('%') == -1)
+      }
+
+      if (tokens[2].indexOf('%') == -1) {
         tokens[2] += '%';
+      }
+
       return colorFunction + '(' + tokens.join(',') + ')';
     });
 
   if (compatibility.colors.opacity && name.indexOf('background') == -1) {
     value = value.replace(/(?:rgba|hsla)\(0,0%?,0%?,0\)/g, function (match) {
-      if (split(value, ',').pop().indexOf('gradient(') > -1)
+      if (split(value, ',').pop().indexOf('gradient(') > -1) {
         return match;
+      }
 
       return 'transparent';
     });
@@ -206,7 +143,113 @@ function colorMininifier(name, value, compatibility) {
   return shortenHex(value);
 }
 
-function pixelLengthMinifier(_, value, compatibility) {
+function optimizeFilters(property) {
+  if (property.value.length == 1) {
+    property.value[0][1] = property.value[0][1].replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\W)/, function (match, filter, suffix) {
+      return filter.toLowerCase() + suffix;
+    });
+  }
+
+  property.value[0][1] = property.value[0][1]
+    .replace(/,(\S)/g, ', $1')
+    .replace(/ ?= ?/g, '=');
+}
+
+function optimizeFont(property, options) {
+  var values = property.value;
+  var hasNumeral = FONT_NUMERAL_WEIGHTS.indexOf(values[0][1]) > -1 ||
+    values[1] && FONT_NUMERAL_WEIGHTS.indexOf(values[1][1]) > -1 ||
+    values[2] && FONT_NUMERAL_WEIGHTS.indexOf(values[2][1]) > -1;
+  var normalCount = 0;
+  var toOptimize;
+
+  if (!options.compatibility.properties.fontWeight) {
+    return;
+  }
+
+  if (hasNumeral) {
+    return;
+  }
+
+  if (values[1] && values[1][1] == '/') {
+    return;
+  }
+
+  if (values[0][1] == 'normal') {
+    normalCount++;
+  }
+
+  if (values[1] && values[1][1] == 'normal') {
+    normalCount++;
+  }
+
+  if (values[2] && values[2][1] == 'normal') {
+    normalCount++;
+  }
+
+  if (normalCount > 1) {
+    return;
+  }
+
+  if (FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[0][1]) > -1) {
+    toOptimize = 0;
+  } else if (values[1] && FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[1][1]) > -1) {
+    toOptimize = 1;
+  } else if (values[2] && FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[2][1]) > -1) {
+    toOptimize = 2;
+  } else if (FONT_NAME_WEIGHTS.indexOf(values[0][1]) > -1) {
+    toOptimize = 0;
+  } else if (values[1] && FONT_NAME_WEIGHTS.indexOf(values[1][1]) > -1) {
+    toOptimize = 1;
+  } else if (values[2] && FONT_NAME_WEIGHTS.indexOf(values[2][1]) > -1) {
+    toOptimize = 2;
+  }
+
+  if (toOptimize !== undefined) {
+    optimizeFontWeight(property, toOptimize);
+    property.dirty = true;
+  }
+}
+
+function optimizeFontWeight(property, atIndex) {
+  var value = property.value[atIndex][1];
+
+  if (value == 'normal') {
+    value = '400';
+  } else if (value == 'bold') {
+    value = '700';
+  }
+
+  property.value[atIndex][1] = value;
+}
+
+function optimizeMultipleZeros(property) {
+  var values = property.value;
+  var spliceAt;
+
+  if (values.length == 4 && values[0][1] === '0' && values[1][1] === '0' && values[2][1] === '0' && values[3][1] === '0') {
+    if (property.name.indexOf('box-shadow') > -1) {
+      spliceAt = 2;
+    } else {
+      spliceAt = 1;
+    }
+  }
+
+  if (spliceAt) {
+    property.value.splice(spliceAt);
+    property.dirty = true;
+  }
+}
+
+function optimizeOutline(property) {
+  var values = property.value;
+
+  if (values.length == 1 && values[0][1] == 'none') {
+    values[0][1] = '0';
+  }
+}
+
+function optimizePixelLengths(_, value, compatibility) {
   if (!WHOLE_PIXEL_VALUE.test(value))
     return value;
 
@@ -214,26 +257,49 @@ function pixelLengthMinifier(_, value, compatibility) {
     var newValue;
     var intVal = parseInt(val);
 
-    if (intVal === 0)
+    if (intVal === 0) {
       return match;
+    }
 
-    if (compatibility.properties.shorterLengthUnits && compatibility.units.pt && intVal * 3 % 4 === 0)
+    if (compatibility.properties.shorterLengthUnits && compatibility.units.pt && intVal * 3 % 4 === 0) {
       newValue = intVal * 3 / 4 + 'pt';
+    }
 
-    if (compatibility.properties.shorterLengthUnits && compatibility.units.pc && intVal % 16 === 0)
+    if (compatibility.properties.shorterLengthUnits && compatibility.units.pc && intVal % 16 === 0) {
       newValue = intVal / 16 + 'pc';
+    }
 
-    if (compatibility.properties.shorterLengthUnits && compatibility.units.in && intVal % 96 === 0)
+    if (compatibility.properties.shorterLengthUnits && compatibility.units.in && intVal % 96 === 0) {
       newValue = intVal / 96 + 'in';
+    }
 
-    if (newValue)
+    if (newValue) {
       newValue = match.substring(0, match.indexOf(val)) + newValue;
+    }
 
     return newValue && newValue.length < match.length ? newValue : match;
   });
 }
 
-function timeUnitMinifier(_, value) {
+function optimizePrecision(_, value, precisionOptions) {
+  var optimizedValue = value.replace(/(\d)\.($|\D)/g, '$1$2');
+
+  if (!precisionOptions.matcher || value.indexOf('.') === -1) {
+    return optimizedValue;
+  }
+
+  return optimizedValue
+    .replace(precisionOptions.matcher, function (match, integerPart, fractionPart, unit) {
+      var multiplier = precisionOptions.units[unit].multiplier;
+      var parsedInteger = parseInt(integerPart);
+      var integer = isNaN(parsedInteger) ? 0 : parsedInteger;
+      var fraction = parseFloat(fractionPart);
+
+      return Math.round((integer + fraction) * multiplier) / multiplier + unit;
+    });
+}
+
+function optimizeTimeUnits(_, value) {
   if (!TIME_VALUE.test(value))
     return value;
 
@@ -250,98 +316,72 @@ function timeUnitMinifier(_, value) {
   });
 }
 
-function minifyBorderRadius(property) {
-  var values = property.value;
-  var spliceAt;
-
-  if (values.length == 3 && values[1][1] == '/' && values[0][1] == values[2][1])
-    spliceAt = 1;
-  else if (values.length == 5 && values[2][1] == '/' && values[0][1] == values[3][1] && values[1][1] == values[4][1])
-    spliceAt = 2;
-  else if (values.length == 7 && values[3][1] == '/' && values[0][1] == values[4][1] && values[1][1] == values[5][1] && values[2][1] == values[6][1])
-    spliceAt = 3;
-  else if (values.length == 9 && values[4][1] == '/' && values[0][1] == values[5][1] && values[1][1] == values[6][1] && values[2][1] == values[7][1] && values[3][1] == values[8][1])
-    spliceAt = 4;
+function optimizeUnits(name, value, unitsRegexp) {
+  if (/^(?:\-moz\-calc|\-webkit\-calc|calc)\(/.test(value)) {
+    return value;
+  }
 
-  if (spliceAt) {
-    property.value.splice(spliceAt);
-    property.dirty = true;
+  if (name == 'flex' || name == '-ms-flex' || name == '-webkit-flex' || name == 'flex-basis' || name == '-webkit-flex-basis') {
+    return value;
   }
-}
 
-function minifyFilter(property) {
-  if (property.value.length == 1) {
-    property.value[0][1] = property.value[0][1].replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\W)/, function (match, filter, suffix) {
-      return filter.toLowerCase() + suffix;
-    });
+  if (value.indexOf('%') > 0 && (name == 'height' || name == 'max-height')) {
+    return value;
   }
 
-  property.value[0][1] = property.value[0][1]
-    .replace(/,(\S)/g, ', $1')
-    .replace(/ ?= ?/g, '=');
+  return value
+    .replace(unitsRegexp, '$1' + '0' + '$2')
+    .replace(unitsRegexp, '$1' + '0' + '$2');
 }
 
-function minifyFont(property, options) {
-  var values = property.value;
-  var hasNumeral = FONT_NUMERAL_WEIGHTS.indexOf(values[0][1]) > -1 ||
-    values[1] && FONT_NUMERAL_WEIGHTS.indexOf(values[1][1]) > -1 ||
-    values[2] && FONT_NUMERAL_WEIGHTS.indexOf(values[2][1]) > -1;
-
-  if (!options.compatibility.properties.fontWeight) {
-    return;
+function optimizeWhitespace(name, value) {
+  if (name.indexOf('filter') > -1 || value.indexOf(' ') == -1 || value.indexOf('expression') === 0) {
+    return value;
   }
 
-  if (hasNumeral)
-    return;
-
-  if (values[1] && values[1][1] == '/')
-    return;
-
-  var normalCount = 0;
-  if (values[0][1] == 'normal')
-    normalCount++;
-  if (values[1] && values[1][1] == 'normal')
-    normalCount++;
-  if (values[2] && values[2][1] == 'normal')
-    normalCount++;
-
-  if (normalCount > 1)
-    return;
+  if (value.indexOf(Marker.SINGLE_QUOTE) > -1 || value.indexOf(Marker.DOUBLE_QUOTE) > -1) {
+    return value;
+  }
 
-  var toOptimize;
-  if (FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[0][1]) > -1)
-    toOptimize = 0;
-  else if (values[1] && FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[1][1]) > -1)
-    toOptimize = 1;
-  else if (values[2] && FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[2][1]) > -1)
-    toOptimize = 2;
-  else if (FONT_NAME_WEIGHTS.indexOf(values[0][1]) > -1)
-    toOptimize = 0;
-  else if (values[1] && FONT_NAME_WEIGHTS.indexOf(values[1][1]) > -1)
-    toOptimize = 1;
-  else if (values[2] && FONT_NAME_WEIGHTS.indexOf(values[2][1]) > -1)
-    toOptimize = 2;
+  value = value.replace(/\s+/g, ' ');
 
-  if (toOptimize !== undefined) {
-    property.value[toOptimize][1] = valueMinifiers['font-weight'](values[toOptimize][1], null, null, options);
-    property.dirty = true;
+  if (value.indexOf('calc') > -1) {
+    value = value.replace(/\) ?\/ ?/g, ')/ ');
   }
-}
 
-function normalizeUrl(value) {
   return value
-    .replace(URL_PREFIX_PATTERN, 'url(')
-    .replace(/\\?\n|\\?\r\n/g, '');
+    .replace(/(\(;?)\s+/g, '$1')
+    .replace(/\s+(;?\))/g, '$1')
+    .replace(/, /g, ',');
 }
 
-function removeUrlQuotes(value) {
-  return /^url\(['"].+['"]\)$/.test(value) && !/^url\(['"].*[\*\s\(\)'"].*['"]\)$/.test(value) && !/^url\(['"]data:[^;]+;charset/.test(value) ?
-    value.replace(/["']/g, '') :
-    value;
+function optimizeZeroDegUnit(_, value) {
+  if (value.indexOf('0deg') == -1) {
+    return value;
+  }
+
+  return value.replace(/\(0deg\)/g, '(0)');
 }
 
-function isQuoted(value) {
-  return QUOTED_PATTERN.test(value);
+function optimizeZeroUnits(name, value) {
+  if (value.indexOf('0') == -1) {
+    return value;
+  }
+
+  if (value.indexOf('-') > -1) {
+    value = value
+      .replace(/([^\w\d\-]|^)\-0([^\.]|$)/g, '$10$2')
+      .replace(/([^\w\d\-]|^)\-0([^\.]|$)/g, '$10$2');
+  }
+
+  return value
+    .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 removeQuotes(name, value) {
@@ -354,6 +394,14 @@ function removeQuotes(name, value) {
     value;
 }
 
+function removeUrlQuotes(value) {
+  return /^url\(['"].+['"]\)$/.test(value) && !/^url\(['"].*[\*\s\(\)'"].*['"]\)$/.test(value) && !/^url\(['"]data:[^;]+;charset/.test(value) ?
+    value.replace(/["']/g, '') :
+    value;
+}
+
+//
+
 function optimizeBody(properties, context) {
   var options = context.options;
   var property, name, type, value;
@@ -384,11 +432,13 @@ function optimizeBody(properties, context) {
       property.unused = true;
     }
 
-    if (name.indexOf('padding') === 0 && (isNegative(property.value[0]) || isNegative(property.value[1]) || isNegative(property.value[2]) || isNegative(property.value[3])))
+    if (name.indexOf('padding') === 0 && (isNegative(property.value[0]) || isNegative(property.value[1]) || isNegative(property.value[2]) || isNegative(property.value[3]))) {
       property.unused = true;
+    }
 
-    if (property.unused)
+    if (property.unused) {
       continue;
+    }
 
     if (property.block) {
       optimizeBody(property.value[0][1], context);
@@ -412,10 +462,6 @@ function optimizeBody(properties, context) {
         break;
       }
 
-      if (valueMinifiers[name]) {
-        value = valueMinifiers[name](value, j, m, options);
-      }
-
       if (valueIsUrl) {
         value = normalizeUrl(value);
         value = options.compatibility.properties.urlQuotes ?
@@ -424,30 +470,37 @@ function optimizeBody(properties, context) {
       } else if (isQuoted(value)) {
         value = removeQuotes(name, value);
       } else {
-        value = whitespaceMinifier(name, value);
-        value = precisionMinifier(name, value, options.precision);
-        value = pixelLengthMinifier(name, value, options.compatibility);
-        value = timeUnitMinifier(name, value);
-        value = zeroMinifier(name, value);
+        value = optimizeWhitespace(name, value);
+        value = optimizePrecision(name, value, options.precision);
+        value = optimizePixelLengths(name, value, options.compatibility);
+        value = optimizeTimeUnits(name, value);
+        value = optimizeZeroUnits(name, value);
         if (options.compatibility.properties.zeroUnits) {
-          value = zeroDegMinifier(name, value);
-          value = unitMinifier(name, value, options.unitsRegexp);
+          value = optimizeZeroDegUnit(name, value);
+          value = optimizeUnits(name, value, options.unitsRegexp);
         }
         if (options.compatibility.properties.colors)
-          value = colorMininifier(name, value, options.compatibility);
+          value = optimizeColors(name, value, options.compatibility);
       }
 
       property.value[j][1] = value;
     }
 
-    multipleZerosMinifier(property);
-
-    if (name.indexOf('border') === 0 && name.indexOf('radius') > 0)
-      minifyBorderRadius(property);
-    else if (name == 'filter')
-      minifyFilter(property);
-    else if (name == 'font')
-      minifyFont(property, options);
+    optimizeMultipleZeros(property);
+
+    if (name == 'background') {
+      optimizeBackground(property);
+    } else if (name.indexOf('border') === 0 && name.indexOf('radius') > 0) {
+      optimizeBorderRadius(property);
+    } else if (name == 'filter') {
+      optimizeFilters(property);
+    } else if (name == 'font') {
+      optimizeFont(property, options);
+    } else if (name == 'font-weight' && options.compatibility.properties.fontWeight) {
+      optimizeFontWeight(property, 0);
+    } else if (name == 'outline') {
+      optimizeOutline(property);
+    }
   }
 
   restoreFromOptimizing(_properties);
@@ -458,10 +511,6 @@ function optimizeBody(properties, context) {
   }
 }
 
-function isUrl(value) {
-  return URL_PREFIX_PATTERN.test(value);
-}
-
 function removeComments(tokens, options) {
   var token;
   var i;
@@ -520,8 +569,9 @@ function buildUnitRegexp(options) {
   var otherUnits = ['ch', 'rem', 'vh', 'vm', 'vmax', 'vmin', 'vw'];
 
   otherUnits.forEach(function (unit) {
-    if (options.compatibility.units[unit])
+    if (options.compatibility.units[unit]) {
       units.push(unit);
+    }
   });
 
   return new RegExp('(^|\\s|\\(|,)0(?:' + units.join('|') + ')(\\W|$)', 'g');