From 3d2cd2a0924667d83caf053d6f8e91cb078014fd Mon Sep 17 00:00:00 2001 From: Jakub Pawlowicz Date: Mon, 9 Jan 2017 14:48:34 +0100 Subject: [PATCH] Reorganizes level 1 optimizations. Why: * Slightly more coherent now, will get more love in 4.1. --- lib/optimizer/level-1/optimize.js | 462 +++++++++++++++++------------- 1 file changed, 256 insertions(+), 206 deletions(-) diff --git a/lib/optimizer/level-1/optimize.js b/lib/optimizer/level-1/optimize.js index fb42d51b..bd7b2b80 100644 --- a/lib/optimizer/level-1/optimize.js +++ b/lib/optimizer/level-1/optimize.js @@ -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'); -- 2.34.1