From 6536cce5b52ac82423eb6e95e75f45e637f95634 Mon Sep 17 00:00:00 2001 From: GoalSmashers Date: Sat, 2 Nov 2013 13:07:41 +0100 Subject: [PATCH] Adds reducing non-adjacent selectors when overridden by more complex selectors. * E.g. "strong{color:red}.one,strong{color:#fff}" can be reduced to ".one,strong{color:#fff}". * Fixes property optimizer when body is empty. * Fixes property optimizer with merging on joins. --- History.md | 1 + lib/properties/optimizer.js | 5 ++- lib/selectors/optimizer.js | 72 +++++++++++++++++++++++++++++++------ test/data/big-min.css | 13 ++++--- test/data/blueprint-min.css | 4 +-- test/unit-test.js | 30 ++++++++++++++++ 6 files changed, 104 insertions(+), 21 deletions(-) diff --git a/History.md b/History.md index d87ea815..5c943070 100644 --- a/History.md +++ b/History.md @@ -16,6 +16,7 @@ * Adds property overriding so more coarse properties override more granular ones. * Adds reducing non-adjacent selectors. * Adds `--skip-advanced`/`noAdvanced` switch to disable advanced optimizations. +* Adds reducing non-adjacent selectors when overridden by more complex selectors. 1.1.7 / 2013-10-28 ================== diff --git a/lib/properties/optimizer.js b/lib/properties/optimizer.js index 8ba36f74..10e67de8 100644 --- a/lib/properties/optimizer.js +++ b/lib/properties/optimizer.js @@ -115,6 +115,9 @@ module.exports = function Optimizer() { for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; + if (token === '') + continue; + var firstColon = token.indexOf(':'); keyValues.push([ token.substring(0, firstColon), @@ -150,7 +153,7 @@ module.exports = function Optimizer() { if (allowAdjacent === false || allowAdjacent === true) return allowAdjacent; - return allowAdjacent.indexOf(position - 1) > -1; + return allowAdjacent.indexOf(position) > -1; }; for (var i = 0, l = tokens.length; i < l; i++) { diff --git a/lib/selectors/optimizer.js b/lib/selectors/optimizer.js index 2ca6af39..b2be39ed 100644 --- a/lib/selectors/optimizer.js +++ b/lib/selectors/optimizer.js @@ -83,21 +83,33 @@ module.exports = function Optimizer(data, options) { var reduceNonAdjacent = function(tokens) { var matched = {}; var matchedMoreThanOnce = []; + var partiallyReduced = []; + var token, selector, selectors; + var removeEmpty = function(value) { + return value.length > 0 ? value : ''; + }; for (var i = 0, l = tokens.length; i < l; i++) { - var token = tokens[i]; - var selector = token.selector; + token = tokens[i]; + selector = token.selector; if (typeof(token) == 'string' || token.block) continue; - var alreadyMatched = matched[selector]; - if (alreadyMatched) { - if (alreadyMatched.length == 1) - matchedMoreThanOnce.push(selector); - alreadyMatched.push(i); - } else { - matched[selector] = [i]; + selectors = selector.split(','); + if (selectors.length > 1) + selectors.unshift(selector); + + for (var j = 0, m = selectors.length; j < m; j++) { + var sel = selectors[j]; + var alreadyMatched = matched[sel]; + if (alreadyMatched) { + if (alreadyMatched.length == 1) + matchedMoreThanOnce.push(sel); + alreadyMatched.push(i); + } else { + matched[sel] = [i]; + } } } @@ -108,7 +120,7 @@ module.exports = function Optimizer(data, options) { for (var j = 0, m = matchPositions.length; j < m; j++) { var body = tokens[matchPositions[j]].body; bodies.push(body); - joinsAt.push((joinsAt[j - 1] || 0) + body.split(';').length - 1); + joinsAt.push((joinsAt[j - 1] || 0) + body.split(';').length); } var optimizedBody = propertyOptimizer.process(bodies.join(';'), joinsAt); @@ -116,16 +128,54 @@ module.exports = function Optimizer(data, options) { var k = optimizedTokens.length - 1; var currentMatch = matchPositions.length - 1; + while (currentMatch >= 0) { if (bodies[currentMatch].indexOf(optimizedTokens[k]) > - 1) { k -= 1; continue; } - tokens[matchPositions[currentMatch]].body = optimizedTokens.splice(k + 1).join(';'); + var tokenIndex = matchPositions[currentMatch]; + var token = tokens[tokenIndex]; + var reducedBody = optimizedTokens + .splice(k + 1) + .filter(removeEmpty) + .join(';'); + + if (token.selector == selector) { + token.body = reducedBody; + } else { + token._partials = token._partials || []; + token._partials.push(reducedBody); + + if (partiallyReduced.indexOf(tokenIndex) == -1) + partiallyReduced.push(tokenIndex); + } + currentMatch -= 1; } }); + + // process those tokens which were partially reduced + // i.e. at least one of token's selectors saw reduction + // if all selectors were reduced to same value we can override it + for (i = 0, l = partiallyReduced.length; i < l; i++) { + token = tokens[partiallyReduced[i]]; + selectors = token.selector.split(','); + + if (token._partials.length == selectors.length && token.body != token._partials[0]) { + var newBody = token._partials[0]; + for (var k = 1, n = token._partials.length; k < n; k++) { + if (token._partials[k] != newBody) + break; + } + + if (k == n) + token.body = newBody; + } + + delete token._partials; + } }; var optimize = function(tokens) { diff --git a/test/data/big-min.css b/test/data/big-min.css index e46fd91b..61b680bc 100644 --- a/test/data/big-min.css +++ b/test/data/big-min.css @@ -25,7 +25,7 @@ sub{bottom:-.25em} ol,ul{margin:0;padding:0;list-style-type:none} dd{margin:0 0 0 40px} nav ol,nav ul{list-style:none;list-style-image:none} -img{border:0;-ms-interpolation-mode:bicubic} +img{-ms-interpolation-mode:bicubic} svg:not(:root){overflow:hidden} figure,form{margin:0} fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} @@ -829,7 +829,7 @@ img[height="97"]+.ico29x29{bottom:6%;left:3.5%} #header .acces_compte a{display:block;color:#000} #header .acces_compte a:hover{color:#000;font-weight:700} .filtre_page{position:fixed;width:100%;height:100%;z-index:15;left:0;top:0;background:rgba(0,0,0,.5)} -.lightbox_ext{background:#fff;width:770px;margin:0 auto;position:absolute;top:100px;left:25%;z-index:15;-webkit-box-shadow:0 0 15px #000;-moz-box-shadow:0 0 15px #000;box-shadow:0 0 15px #000} +.lightbox_ext{width:770px;margin:0 auto;position:absolute;top:100px;left:25%;z-index:15;-webkit-box-shadow:0 0 15px #000;-moz-box-shadow:0 0 15px #000;box-shadow:0 0 15px #000} .lightbox_ext,.loginbox{overflow:hidden;background:#f5f8f9} .lightbox_ext h2,.loginbox h2{padding:7px 16px 5px;background:#16212c;border-top:3px solid #747b83;color:#fff} .lightbox_ext .fermer,.loginbox .fermer{float:right;font-size:11px;line-height:18px;color:#747b83;cursor:pointer} @@ -881,12 +881,12 @@ label i{font-style:normal;display:none} .ombrelle .autopromo_edito{overflow:hidden;width:314px;height:64px} .autopromo_edito img{border:1px solid #eef1f5} .autopromo_edito .nature_edito{display:inline} -#nav{clear:both;height:32px;margin:0 auto;width:1000px;background:#fafbfc;border-top:3px solid transparent;border-bottom:1px solid #dddee0} +#nav{clear:both;height:32px;margin:0 auto;width:1000px;background:#fafbfc;border-bottom:1px solid #dddee0} #nav .conteneur_bordure{width:998px;margin:-3px auto 0;border-top:3px solid #ffd500;border-left:1px solid #d2d6db;border-right:1px solid #d2d6db} #nav.accueil{width:auto;background:#fff} #nav ul{overflow:hidden;width:1000px;margin:-3px auto 0} #nav.acceuil ul{width:998px} -#nav li{display:block;float:left;border-top-width:3px;border-top-style:solid} +#nav li{display:block;float:left} #nav a,#nav span{display:inline-block;height:25px;padding:7px 10px 0 9px;border-left:1px solid #d2d6db;border-bottom:1px solid #d2d6db;font-size:12px;font-weight:700;text-transform:uppercase;color:#000} #nav .obf:hover,#nav a:hover,#nav li:hover .obf,#nav li:hover a{color:#fff} #nav .actif{background:#fff} @@ -1093,7 +1093,6 @@ label i{font-style:normal;display:none} .bloc_part.gymglish .exercice .texte,.bloc_part.gymglish .mot_mois .texte{width:166px;height:126px;padding:0} .bloc_part.gymglish .mot_mois .texte{width:155px;padding:4px 15px 0 0} .bloc_part.gymglish .cours .texte{width:145px;height:170px;padding:0 15px 0 0} -.bloc_part.gymglish .cours .img{width:120px} .bloc_part.gymglish .mot_mois .enonce{display:block;overflow:hidden;height:45px;clear:both} .bloc_part.gymglish .exercice.contenu{position:relative;background:#FFF} .bloc_part.gymglish .exercice.contenu .btn_fonce{left:16px} @@ -1644,7 +1643,7 @@ ul.errorlist li{font-size:11px;font-weight:400;color:#e20000} #core-liberation .pagination{height:21px} #core-liberation .pagination a{display:block;float:left;background:#e6e6e6;height:19px;margin-right:5px;padding:1px 6px} #core-liberation .pagination span{display:inline;height:19px;margin-right:5px;padding:1px 6px} -#core-liberation .pagination .current,#core-liberation .pagination .disabled{background:#7d7d7d;color:#fff} +#core-liberation .pagination .current,#core-liberation .pagination .disabled{background:#7d7d7d} #core-liberation .js-loader{width:0;height:0} img#hit-count{position:absolute;bottom:0;right:0;margin:0;padding:0;height:0} #DOMWindow iframe{height:96%!important} @@ -2778,7 +2777,7 @@ body.slideshow .ad-top .megaban{background:#333} .site-liberation .block-call-items .tpl-search-results .object-picture .np a.date{background-color:#c8c8c8} #core-liberation .block-item .object-picture .legende{color:#838383} .site-liberation .block-item .object-content a:hover{color:#e20000} -#core-liberation .block-item-locked{border-top-color:#c1b0bb;border-bottom-color:#c1b0bb;background:-moz-linear-gradient(top,#dbdad6 0,#fff 2%,#fff 98%,#e1e0de 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#dbdad6),color-stop(2%,#fff),color-stop(98%,#fff),color-stop(100%,#e1e0de));background:-o-linear-gradient(top,#dbdad6 0,#fff 2%,#fff 98%,#e1e0de 100%)} +#core-liberation .block-item-locked{border-top-color:#c1b0bb;border-bottom-color:#c1b0bb;background:#fff;background:-moz-linear-gradient(top,#dbdad6 0,#fff 2%,#fff 98%,#e1e0de 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#dbdad6),color-stop(2%,#fff),color-stop(98%,#fff),color-stop(100%,#e1e0de));background:-o-linear-gradient(top,#dbdad6 0,#fff 2%,#fff 98%,#e1e0de 100%)} #core-liberation .block-item-locked .block-top span{background-color:#fff} #core-liberation .block-item-locked .btn-zoneabo a:hover{color:#fff} #core-liberation .block-item-read-more{border-color:#ddd} diff --git a/test/data/blueprint-min.css b/test/data/blueprint-min.css index 6c79b21a..f6794f13 100644 --- a/test/data/blueprint-min.css +++ b/test/data/blueprint-min.css @@ -63,7 +63,7 @@ caption{background:#eee} .bottom{margin-bottom:0;padding-bottom:0} label{font-weight:700} fieldset{padding:0 1.4em 1.4em;margin:0 0 1.5em;border:1px solid #ccc} -legend{font-weight:700;font-size:1.2em;margin-top:-.2em;margin-bottom:1em} +legend{font-weight:700;font-size:1.2em} #IE8#HACK,fieldset{padding-top:1.4em} #IE8#HACK,legend{margin-top:0;margin-bottom:0} input.text,input.title,input[type=password],input[type=text],textarea{background-color:#fff;border:1px solid #bbb} @@ -242,4 +242,4 @@ hr{background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margin hr.space{background:#fff;color:#fff;visibility:hidden} .clearfix:after,.container:after{content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden} .clearfix,.container{display:block} -.clear{clear:both} \ No newline at end of file +.clear{clear:both} diff --git a/test/unit-test.js b/test/unit-test.js index ff105bae..96131ce5 100644 --- a/test/unit-test.js +++ b/test/unit-test.js @@ -1183,6 +1183,10 @@ title']{display:block}", 'of two adjacent complex selectors with different selector order': [ '.one,.two{color:red}.two,.one{line-height:1em}', '.one,.two{color:red;line-height:1em}' + ], + 'two adjacent with hex color definitions': [ + "a:link,a:visited{color:#fff}.one{display:block}a:link,a:visited{color:red}", + ".one{display:block}a:link,a:visited{color:red}" ] }), 'same non-adjacent selectors': cssContext({ @@ -1195,6 +1199,10 @@ title']{display:block}", 'a{display:inline-block;display:-moz-inline-box;color:red}.one{font-size:12px}a{color:#fff;margin:2px}', 'a{display:inline-block;display:-moz-inline-box}.one{font-size:12px}a{color:#fff;margin:2px}' ], + 'with intentionally redefined properties on nultiple joins': [ + 'a{color:red}.one{font-size:12px}a{color:#fff;margin:2px}.two{font-weight:400}a{margin:0}', + '.one{font-size:12px}a{color:#fff}.two{font-weight:400}a{margin:0}' + ], 'with all redefined properties': [ 'a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}', '.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}' @@ -1202,6 +1210,28 @@ title']{display:block}", 'many with all redefined properties': [ 'a{padding:10px}.zero{color:transparent}a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}', 'a{padding:10px}.zero{color:transparent}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}' + ], + 'when overriden by an empty selector': [ + 'a{padding:10px}.one{color:red}a{}', + 'a{padding:10px}.one{color:red}' + ], + 'when overriden by a complex selector': [ + 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}', + 'a{margin:0}.one{color:red}a,p{color:red;padding:0}' + ], + 'when overriden by complex selectors': [ + 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}.one,a{color:#fff}', + 'a{margin:0}a,p{color:red;padding:0}.one,a{color:#fff}' + ], + 'when complex selector overriden by simple selectors': 'a,p{margin:0;color:red}a{color:#fff}', + // Pending re-run selectors merge - see #160 + 'when complex selector overriden by complex and simple selectors': [ + 'a,p{margin:0;color:red}a{color:#fff}a,p{color:#00f}p{color:#0f0}', + 'a,p{margin:0}a,p{color:#00f}p{color:#0f0}' + ], + 'when complex selector overriden by complex selectors': [ + '.one>.two,.three{color:red;line-height:1rem}#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}', + '#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}' ] }), 'same bodies': cssContext({ -- 2.34.1