From: Timur Kristóf Date: Fri, 28 Feb 2014 14:49:05 +0000 (+0100) Subject: New optimizer supports border-radius, border-color, border-style, border-width X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=b0ed832c3b169c2b3c4138ba04eda68017ccf035;p=clean-css.git New optimizer supports border-radius, border-color, border-style, border-width --- diff --git a/lib/clean.js b/lib/clean.js index ed00f51a..05df04a4 100644 --- a/lib/clean.js +++ b/lib/clean.js @@ -12,7 +12,6 @@ var ColorHSLToHex = require('./colors/hsl-to-hex'); var ColorRGBToHex = require('./colors/rgb-to-hex'); var ColorLongToShortHex = require('./colors/long-to-short-hex'); -var ShorthandNotations = require('./properties/shorthand-notations'); var ImportInliner = require('./imports/inliner'); var UrlRebase = require('./images/url-rebase'); var EmptyRemoval = require('./selectors/empty-removal'); @@ -308,10 +307,6 @@ var minify = function(data, callback) { replace(/:0 0 0 0([^\.])/g, ':0$1'); replace(/([: ,=\-])0\.(\d)/g, '$1.$2'); - replace(function shorthandNotations() { - data = new ShorthandNotations(data).process(); - }); - // restore rect(...) zeros syntax for 4 zeros replace(/rect\(\s?0(\s|,)0[ ,]0[ ,]0\s?\)/g, 'rect(0$10$10$10)'); diff --git a/lib/properties/processable.js b/lib/properties/processable.js index c339e000..91e00e0a 100644 --- a/lib/properties/processable.js +++ b/lib/properties/processable.js @@ -90,13 +90,12 @@ module.exports = (function () { canOverride = Object.freeze(canOverride); // Functions for breaking up shorthands to components - var breakUp = { - // Use this for properties with 4 unit values (like margin or padding) - // NOTE: it handles shorter forms of these properties too (like, only 1, 2, or 3 units) - fourUnits: function (token) { + var breakUp = {}; + breakUp.takeCareOfFourValues = function (splitfunc) { + return function (token) { var descriptor = processable[token.prop]; var result = []; - var splitval = token.value.match(new RegExp(validator.cssUnitAnyRegexStr, 'gi')); + var splitval = splitfunc(token.value); if (splitval.length === 0 || (splitval.length < descriptor.components.length && descriptor.components.length > 4)) { // This token is malformed and we have no idea how to fix it. So let's just keep it intact @@ -124,150 +123,192 @@ module.exports = (function () { } return result; - }, - // Breaks up a background property value - background: function (token) { - // Default values - var result = Token.makeDefaults(['background-color', 'background-image', 'background-repeat', 'background-position', 'background-attachment'], token.isImportant); - var color = result[0], image = result[1], repeat = result[2], position = result[3], attachment = result[4]; - - // Take care of inherit - if (token.value === 'inherit') { - // NOTE: 'inherit' is not a valid value for background-attachment so there we'll leave the default value - color.value = image.value = repeat.value = position.value = attachment.value = 'inherit'; - return result; + }; + }; + // Use this for properties with 4 unit values (like margin or padding) + // NOTE: it handles shorter forms of these properties too (like, only 1, 2, or 3 units) + breakUp.fourUnits = breakUp.takeCareOfFourValues(function (val) { + return val.match(new RegExp(validator.cssUnitAnyRegexStr, 'gi')); + }); + // Use this when you simply want to break up four values along spaces + breakUp.fourBySpaces = breakUp.takeCareOfFourValues(function (val) { + return val.split(' ').filter(function (v) { return v; }); + }); + // Use this for non-length values that can also contain functions + breakUp.fourBySpacesOrFunctions = breakUp.takeCareOfFourValues(function (val) { + var result = []; + var curr = ''; + var parenthesisLevel = 0; + + for (var i = 0; i < val.length; i++) { + var c = val[i]; + curr += c; + + if (c === '(') { + parenthesisLevel++; + } + else if (c === ')') { + parenthesisLevel--; + if (parenthesisLevel === 0) { + result.push(curr.trim()); + curr = ''; + } } - - // Break the background up into parts - var parts = token.value.split(' '); - if (parts.length === 0) { - return result; + else if (c === ' ' && parenthesisLevel === 0) { + result.push(curr.trim()); + curr = ''; } + } - // The trick here is that we start going through the parts from the end, then stop after background repeat, - // then start from the from the beginning until we find a valid color value. What remains will be treated as background-image. - - var currentIndex = parts.length - 1; - var current = parts[currentIndex]; - // Attachment - if (validator.isValidBackgroundAttachment(current)) { - // Found attachment - attachment.value = current; - currentIndex--; - current = parts[currentIndex]; - } - // Position - var pos = parts[currentIndex - 1] + ' ' + parts[currentIndex]; - if (currentIndex >= 1 && validator.isValidBackgroundPosition(pos)) { - // Found position (containing two parts) - position.value = pos; - currentIndex -= 2; - current = parts[currentIndex]; - } - else if (currentIndex >= 0 && validator.isValidBackgroundPosition(current)) { - // Found position (containing just one part) - position.value = current; - currentIndex--; - current = parts[currentIndex]; - } - // Repeat - if (currentIndex >= 0 && validator.isValidBackgroundRepeat(current)) { - // Found repeat - repeat.value = current; - currentIndex--; - current = parts[currentIndex]; - } - // Color - var fromBeginning = 0; - if (validator.isValidColor(parts[0])) { - // Found color - color.value = parts[0]; - fromBeginning = 1; - } - // Image - image.value = (parts.splice(fromBeginning, currentIndex - fromBeginning + 1).join(' ')) || 'none'; + if (curr) { + result.push(curr.trim()); + curr = ''; + } + return result; + }); + // Breaks up a background property value + breakUp.background = function (token) { + // Default values + var result = Token.makeDefaults(['background-color', 'background-image', 'background-repeat', 'background-position', 'background-attachment'], token.isImportant); + var color = result[0], image = result[1], repeat = result[2], position = result[3], attachment = result[4]; + + // Take care of inherit + if (token.value === 'inherit') { + // NOTE: 'inherit' is not a valid value for background-attachment so there we'll leave the default value + color.value = image.value = repeat.value = position.value = attachment.value = 'inherit'; return result; - }, - // Breaks up a list-style property value - listStyle: function (token) { - // Default values - var result = Token.makeDefaults(['list-style-type', 'list-style-position', 'list-style-image'], token.isImportant); - var type = result[0], position = result[1], image = result[2]; - - if (token.value === 'inherit') { - type.value = position.value = image.value = 'inherit'; - return result; - } + } - var parts = token.value.split(' '); - var ci = 0; + // Break the background up into parts + var parts = token.value.split(' '); + if (parts.length === 0) { + return result; + } - // Type - if (ci < parts.length && validator.isValidListStyleType(parts[ci])) { - type.value = parts[ci]; - ci++; - } - // Position - if (ci < parts.length && validator.isValidListStylePosition(parts[ci])) { - position.value = parts[ci]; - ci++; - } - // Image - if (ci < parts.length) { - image.value = parts.splice(ci, parts.length - ci + 1).join(' '); - } + // The trick here is that we start going through the parts from the end, then stop after background repeat, + // then start from the from the beginning until we find a valid color value. What remains will be treated as background-image. + + var currentIndex = parts.length - 1; + var current = parts[currentIndex]; + // Attachment + if (validator.isValidBackgroundAttachment(current)) { + // Found attachment + attachment.value = current; + currentIndex--; + current = parts[currentIndex]; + } + // Position + var pos = parts[currentIndex - 1] + ' ' + parts[currentIndex]; + if (currentIndex >= 1 && validator.isValidBackgroundPosition(pos)) { + // Found position (containing two parts) + position.value = pos; + currentIndex -= 2; + current = parts[currentIndex]; + } + else if (currentIndex >= 0 && validator.isValidBackgroundPosition(current)) { + // Found position (containing just one part) + position.value = current; + currentIndex--; + current = parts[currentIndex]; + } + // Repeat + if (currentIndex >= 0 && validator.isValidBackgroundRepeat(current)) { + // Found repeat + repeat.value = current; + currentIndex--; + current = parts[currentIndex]; + } + // Color + var fromBeginning = 0; + if (validator.isValidColor(parts[0])) { + // Found color + color.value = parts[0]; + fromBeginning = 1; + } + // Image + image.value = (parts.splice(fromBeginning, currentIndex - fromBeginning + 1).join(' ')) || 'none'; + return result; + }; + // Breaks up a list-style property value + breakUp.listStyle = function (token) { + // Default values + var result = Token.makeDefaults(['list-style-type', 'list-style-position', 'list-style-image'], token.isImportant); + var type = result[0], position = result[1], image = result[2]; + + if (token.value === 'inherit') { + type.value = position.value = image.value = 'inherit'; return result; - }, - // Breaks up outline - outline: function (token) { - // Default values - var result = Token.makeDefaults(['outline-color', 'outline-style', 'outline-width'], token.isImportant); - var color = result[0], style = result[1], width = result[2]; - - // Take care of inherit - if (token.value === 'inherit' || token.value === 'inherit inherit inherit') { - color.value = style.value = width.value = 'inherit'; - return result; - } + } - // NOTE: usually users don't follow the required order of parts in this shorthand, - // so we'll try to parse it caring as little about order as possible + var parts = token.value.split(' '); + var ci = 0; - var parts = token.value.split(' '), w; + // Type + if (ci < parts.length && validator.isValidListStyleType(parts[ci])) { + type.value = parts[ci]; + ci++; + } + // Position + if (ci < parts.length && validator.isValidListStylePosition(parts[ci])) { + position.value = parts[ci]; + ci++; + } + // Image + if (ci < parts.length) { + image.value = parts.splice(ci, parts.length - ci + 1).join(' '); + } - if (parts.length === 0) { - return result; - } + return result; + }; + // Breaks up outline + breakUp.outline = function (token) { + // Default values + var result = Token.makeDefaults(['outline-color', 'outline-style', 'outline-width'], token.isImportant); + var color = result[0], style = result[1], width = result[2]; + + // Take care of inherit + if (token.value === 'inherit' || token.value === 'inherit inherit inherit') { + color.value = style.value = width.value = 'inherit'; + return result; + } - if (parts.length >= 1) { - // Try to find outline-width, excluding inherit because that can be anything - w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineWidth(p); }); - if (w.length) { - width.value = w[0]; - parts.splice(parts.indexOf(w[0]), 1); - } + // NOTE: usually users don't follow the required order of parts in this shorthand, + // so we'll try to parse it caring as little about order as possible + + var parts = token.value.split(' '), w; + + if (parts.length === 0) { + return result; + } + + if (parts.length >= 1) { + // Try to find outline-width, excluding inherit because that can be anything + w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineWidth(p); }); + if (w.length) { + width.value = w[0]; + parts.splice(parts.indexOf(w[0]), 1); } - if (parts.length >= 1) { - // Try to find outline-style, excluding inherit because that can be anything - w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineStyle(p); }); - if (w.length) { - style.value = w[0]; - parts.splice(parts.indexOf(w[0]), 1); - } + } + if (parts.length >= 1) { + // Try to find outline-style, excluding inherit because that can be anything + w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineStyle(p); }); + if (w.length) { + style.value = w[0]; + parts.splice(parts.indexOf(w[0]), 1); } - if (parts.length >= 1) { - // Find outline-color but this time can catch inherit - w = parts.filter(function(p) { return validator.isValidOutlineColor(p); }); - if (w.length) { - color.value = w[0]; - parts.splice(parts.indexOf(w[0]), 1); - } + } + if (parts.length >= 1) { + // Find outline-color but this time can catch inherit + w = parts.filter(function(p) { return validator.isValidOutlineColor(p); }); + if (w.length) { + color.value = w[0]; + parts.splice(parts.indexOf(w[0]), 1); } - - return result; } + + return result; }; // Contains functions that can put together shorthands from their components @@ -465,60 +506,12 @@ module.exports = (function () { // Puts the shorthand together from its components. // var processable = { - 'margin': { - components: [ - 'margin-top', - 'margin-right', - 'margin-bottom', - 'margin-left' - ], - breakUp: breakUp.fourUnits, - putTogether: putTogether.takeCareOfInherit(putTogether.fourUnits), - defaultValue: '0' - }, - 'margin-top': { - defaultValue: '0', - canOverride: canOverride.unit - }, - 'margin-right': { - defaultValue: '0', - canOverride: canOverride.unit - }, - 'margin-bottom': { - defaultValue: '0', - canOverride: canOverride.unit - }, - 'margin-left': { - defaultValue: '0', - canOverride: canOverride.unit - }, - 'padding': { - components: [ - 'padding-top', - 'padding-right', - 'padding-bottom', - 'padding-left' - ], - breakUp: breakUp.fourUnits, - putTogether: putTogether.takeCareOfInherit(putTogether.fourUnits), - defaultValue: '0' - }, - 'padding-top': { - defaultValue: '0', - canOverride: canOverride.unit - }, - 'padding-right': { - defaultValue: '0', - canOverride: canOverride.unit - }, - 'padding-bottom': { - defaultValue: '0', - canOverride: canOverride.unit - }, - 'padding-left': { - defaultValue: '0', - canOverride: canOverride.unit + 'color': { + canOverride: canOverride.color, + defaultValue: 'transparent', + shortestValue: 'red' }, + // background ------------------------------------------------------------------------------ 'background': { components: [ 'background-color', @@ -532,11 +525,6 @@ module.exports = (function () { defaultValue: '0 0', shortestValue: '0' }, - 'color': { - canOverride: canOverride.color, - defaultValue: 'transparent', - shortestValue: 'red' - }, 'background-color': { canOverride: canOverride.color, defaultValue: 'transparent', @@ -559,6 +547,7 @@ module.exports = (function () { canOverride: canOverride.always, defaultValue: 'scroll' }, + // list-style ------------------------------------------------------------------------------ 'list-style': { components: [ 'list-style-type', @@ -587,6 +576,7 @@ module.exports = (function () { canOverride: canOverride.always, defaultValue: 'none' }, + // outline ------------------------------------------------------------------------------ 'outline': { components: [ 'outline-color', @@ -611,9 +601,88 @@ module.exports = (function () { defaultValue: 'medium', shortestValue: '0' } - // TODO: add more }; + var addFourValueShorthand = function (prop, components, breakup, puttogether, canoverride, defaultValue, shortestValue) { + processable[prop] = { + components: components, + breakUp: breakup || breakUp.fourUnits, + putTogether: puttogether || putTogether.takeCareOfInherit(putTogether.fourUnits), + defaultValue: defaultValue || '0', + shortestValue: shortestValue + }; + for (var i = 0; i < components.length; i++) { + processable[components[i]] = { + canOverride: canoverride || canOverride.unit, + defaultValue: defaultValue || '0', + shortestValue: shortestValue + }; + } + }; + + addFourValueShorthand('border-radius', [ + 'border-top-left-radius', + 'border-top-right-radius', + 'border-bottom-right-radius', + 'border-bottom-left-radius' + ]); + + addFourValueShorthand('-moz-border-radius', [ + '-moz-border-top-left-radius', + '-moz-border-top-right-radius', + '-moz-border-bottom-right-radius', + '-moz-border-bottom-left-radius' + ]); + + addFourValueShorthand('-webkit-border-radius', [ + '-webkit-border-top-left-radius', + '-webkit-border-top-right-radius', + '-webkit-border-bottom-right-radius', + '-webkit-border-bottom-left-radius' + ]); + + addFourValueShorthand('-o-border-radius', [ + '-o-border-top-left-radius', + '-o-border-top-right-radius', + '-o-border-bottom-right-radius', + '-o-border-bottom-left-radius' + ]); + + addFourValueShorthand('border-color', [ + 'border-top-color', + 'border-right-color', + 'border-bottom-color', + 'border-left-color' + ], breakUp.fourBySpacesOrFunctions, null, canOverride.color, 'currentColor', 'red'); + + addFourValueShorthand('border-style', [ + 'border-top-style', + 'border-right-style', + 'border-bottom-style', + 'border-left-style' + ], breakUp.fourBySpaces, null, canOverride.always, 'none'); + + addFourValueShorthand('border-width', [ + 'border-top-width', + 'border-right-width', + 'border-bottom-width', + 'border-left-width' + ], null, null, null, 'medium', '0'); + + addFourValueShorthand('padding', [ + 'padding-top', + 'padding-right', + 'padding-bottom', + 'padding-left' + ]); + + addFourValueShorthand('margin', [ + 'margin-top', + 'margin-right', + 'margin-bottom', + 'margin-left' + ]); + // Set some stuff iteratively for (var proc in processable) { if (processable.hasOwnProperty(proc)) { diff --git a/lib/properties/shorthand-notations.js b/lib/properties/shorthand-notations.js deleted file mode 100644 index f29b2d36..00000000 --- a/lib/properties/shorthand-notations.js +++ /dev/null @@ -1,50 +0,0 @@ -module.exports = function ShorthandNotations(data) { - // shorthand notations - var shorthandRegex = function(repeats, hasSuffix) { - var pattern = '(padding|margin|border\\-width|border\\-color|border\\-style|border\\-radius):'; - for (var i = 0; i < repeats; i++) - pattern += '([\\d\\w\\.%#\\(\\),]+)' + (i < repeats - 1 ? ' ' : ''); - return new RegExp(pattern + (hasSuffix ? '([;}])' : ''), 'g'); - }; - - var from4Values = function() { - return data.replace(shorthandRegex(4), function(match, property, size1, size2, size3, size4) { - if (size1 === size2 && size1 === size3 && size1 === size4) - return property + ':' + size1; - else if (size1 === size3 && size2 === size4) - return property + ':' + size1 + ' ' + size2; - else if (size2 === size4) - return property + ':' + size1 + ' ' + size2 + ' ' + size3; - else - return match; - }); - }; - - var from3Values = function() { - return data.replace(shorthandRegex(3, true), function(match, property, size1, size2, size3, suffix) { - if (size1 === size2 && size1 === size3) - return property + ':' + size1 + suffix; - else if (size1 === size3) - return property + ':' + size1 + ' ' + size2 + suffix; - else - return match; - }); - }; - - var from2Values = function() { - return data.replace(shorthandRegex(2, true), function(match, property, size1, size2, suffix) { - if (size1 === size2) - return property + ':' + size1 + suffix; - else - return match; - }); - }; - - return { - process: function() { - data = from4Values(); - data = from3Values(); - return from2Values(); - } - }; -}; diff --git a/test/data/big-min.css b/test/data/big-min.css index 8c094cd2..7f12fa95 100644 --- a/test/data/big-min.css +++ b/test/data/big-min.css @@ -2194,7 +2194,7 @@ a.god:hover{background:#3c3c3c;color:#fff;text-decoration:none} #core-liberation .block-search-results .block-content .object-picture img{display:block;width:87px} #core-liberation .block-search-results .block-content .article .object-picture,#core-liberation .block-search-results .block-content .article .object-picture img{height:116px} #core-liberation .block-search-results .block-content .object-picture .np{position:relative} -#core-liberation .block-search-results .block-content .object-picture .np .p1,#core-liberation .block-search-results .block-content .object-picture .np .p2{position:absolute;width:79px;height:102px;border-style:solid;border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px} +#core-liberation .block-search-results .block-content .object-picture .np .p1,#core-liberation .block-search-results .block-content .object-picture .np .p2{position:absolute;width:79px;height:102px;border-style:solid;border-width:7px 4px} #core-liberation .block-search-results .block-content .object-picture .np a.date{position:absolute;display:block;width:80px;top:10px;padding:2px 2px 3px 4px} #core-liberation .block-search-results .block-content .object-picture .np .p1{z-index:2000} #core-liberation .block-search-results .block-content .object-picture .np .p2{z-index:1000} diff --git a/test/unit-test.js b/test/unit-test.js index 86ee3c9a..4f773190 100644 --- a/test/unit-test.js +++ b/test/unit-test.js @@ -1633,13 +1633,33 @@ title']{display:block}", ] }), 'shorthand properties': cssContext({ - 'shorthand background' : [ + 'shorthand background #1' : [ 'div{background-color:#111;background-image:url(aaa);background-repeat:repeat;background-position:0 0;background-attachment:scroll}', 'div{background:#111 url(aaa)}' ], + 'shorthand background #2' : [ + 'div{background-color:#111;background-image:url(aaa);background-repeat:no-repeat;background-position:0 0;background-attachment:scroll}', + 'div{background:#111 url(aaa) no-repeat}' + ], 'shorthand important background' : [ 'div{background-color:#111!important;background-image:url(aaa)!important;background-repeat:repeat!important;background-position:0 0!important;background-attachment:scroll!important}', 'div{background:#111 url(aaa)!important}' + ], + 'shorthand border-width': [ + '.t{border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px}', + '.t{border-width:7px 4px}' + ], + 'shorthand border-color #1': [ + '.t{border-top-color:#9fce00;border-bottom-color:#9fce00;border-left-color:#9fce00;border-right-color:#9fce00}', + '.t{border-color:#9fce00}' + ], + 'shorthand border-color #2': [ + '.t{border-right-color:#002;border-bottom-color:#003;border-top-color:#001;border-left-color:#004}', + '.t{border-color:#001 #002 #003 #004}' + ], + 'shorthand border-radius': [ + '.t{border-top-left-radius:7px;border-bottom-right-radius:6px;border-bottom-left-radius:5px;border-top-right-radius:3px}', + '.t{border-radius:7px 3px 6px 5px}' ] }), 'care about understandability of shorthand components': cssContext({ @@ -1788,7 +1808,14 @@ title']{display:block}", }), 'complex granular properties': cssContext({ 'two granular properties': 'a{border-bottom:1px solid red;border-color:red}', - 'two same granular properties': 'a{border-color:rgba(0,0,0,.5);border-color:red}', + 'more understandable granular property should override less understandable': [ + 'a{border-color:rgba(0,0,0,.5);border-color:red}', + 'a{border-color:red}' + ], + 'less understandable granular property should NOT override more understandable': [ + 'a{border-color:red;border-color:rgba(0,0,0,.5)}', + 'a{border-color:red;border-color:rgba(0,0,0,.5)}' + ], 'two same granular properties redefined': [ 'a{border-color:rgba(0,0,0,.5);border-color:red;border:0}', 'a{border:0}'