Adds reducing non-adjacent selectors when overridden by more complex selectors.
authorGoalSmashers <jakub@goalsmashers.com>
Sat, 2 Nov 2013 12:07:41 +0000 (13:07 +0100)
committerGoalSmashers <jakub@goalsmashers.com>
Sun, 3 Nov 2013 08:49:23 +0000 (09:49 +0100)
* 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
lib/properties/optimizer.js
lib/selectors/optimizer.js
test/data/big-min.css
test/data/blueprint-min.css
test/unit-test.js

index d87ea81..5c94307 100644 (file)
@@ -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
 ==================
index 8ba36f7..10e67de 100644 (file)
@@ -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++) {
index 2ca6af3..b2be39e 100644 (file)
@@ -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) {
index e46fd91..61b680b 100644 (file)
@@ -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}
index 6c79b21..f6794f1 100644 (file)
@@ -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}
index ff105ba..96131ce 100644 (file)
@@ -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({