Adds merging adjacent selectors within a scope (single and multiple ones).
authorGoalSmashers <jakub@goalsmashers.com>
Mon, 28 Oct 2013 10:28:44 +0000 (11:28 +0100)
committerGoalSmashers <jakub@goalsmashers.com>
Sat, 2 Nov 2013 16:30:38 +0000 (17:30 +0100)
* Merges properties inside merged selectors too.
* Adds merging two adjacent properties when merging selectors.

History.md
lib/selectors/optimizer.js
test/data/big-min.css
test/unit-test.js

index 15f271d..9c21858 100644 (file)
@@ -10,6 +10,7 @@
 * Adds CSS tokenizer which will make it possible to optimize content by reordering and/or merging selectors.
 * Adds basic optimizer removing duplicate selectors from a list.
 * Adds merging duplicate properties within a single selector's body.
+* Adds merging adjacent selectors within a scope (single and multiple ones).
 
 1.1.7 / 2013-10-28
 ==================
index c4f6246..3395437 100644 (file)
@@ -15,7 +15,7 @@ module.exports = function Optimizer(data) {
     return plain.join(',');
   };
 
-  var mergeProperties = function(body) {
+  var mergeProperties = function(body, allowAdjacent) {
     var merged = [];
     var properties = [];
     var flat = [];
@@ -36,7 +36,8 @@ module.exports = function Optimizer(data) {
 
       // comment is necessary - we assume that if two keys are one after another
       // then it is intentional way of redefining property which may not be widely supported
-      if (alreadyIn > -1 && lastKey != key) {
+      // however if `allowAdjacent` is set then the rule does not apply (see merging two adjacent selectors)
+      if (alreadyIn > -1 && (allowAdjacent || lastKey != key)) {
         merged.splice(alreadyIn, 1);
         properties.splice(alreadyIn, 1);
       }
@@ -81,6 +82,28 @@ module.exports = function Optimizer(data) {
     }
   };
 
+  var mergeAdjacent = function(tokens) {
+    var forMerging = [];
+    var lastSelector = null;
+
+    for (var i = 0, l = tokens.length; i < l; i++) {
+      if (typeof(tokens[i]) == 'string' || tokens[i].block)
+        continue;
+
+      var selector = tokens[i].selector;
+      if (lastSelector == selector)
+        forMerging.push(i);
+
+      lastSelector = selector;
+    }
+
+    for (var j = 0, m = forMerging.length; j < m; j++) {
+      var position = forMerging[j] - j;
+      tokens[position - 1].body = mergeProperties(tokens[position - 1].body + ';' + tokens[position].body, true);
+      tokens.splice(position, 1);
+    }
+  };
+
   var optimize = function(tokens) {
     tokens = (Array.isArray(tokens) ? tokens : [tokens]);
     for (var i = 0, l = tokens.length; i < l; i++) {
@@ -88,13 +111,14 @@ module.exports = function Optimizer(data) {
 
       if (token.selector) {
         token.selector = stripRepeats(token.selector);
-        token.body = mergeProperties(token.body);
+        token.body = mergeProperties(token.body, false);
       } else if (token.block) {
         optimize(token.body);
       }
     }
 
     removeDuplicates(tokens);
+    mergeAdjacent(tokens);
   };
 
   var rebuild = function(tokens) {
index 65132e9..332204b 100644 (file)
@@ -483,8 +483,7 @@ article .liste_carre_999{margin-top:5px}
 .fleuve .urgent .grid_1 .tt13_capital{padding:0 0 10px;border-bottom:1px solid #cb2626}
 .fleuve .conteneur_fleuve{background:#fff;margin-left:0;padding:0;width:605px}
 .fleuve .urgent .conteneur_fleuve{padding-top:10px}
-.fleuve .jour_parution{background:#fff}
-.fleuve .jour_parution{display:block;padding:0 0 20px;color:#2e3942;text-transform:uppercase;font-weight:700}
+.fleuve .jour_parution{background:#fff;display:block;padding:0 0 20px;color:#2e3942;text-transform:uppercase;font-weight:700}
 .fleuve .atome{margin:0 0 10px}
 .fleuve .liens{margin:16px 0 0;color:#a2a9ae}
 .fleuve .liens>span:first-child{float:left}
@@ -826,8 +825,7 @@ img[height="97"]+.ico29x29{bottom:6%;left:3.5%}
 #header .acces_compte .avatar_nom{height:26px;margin:3px 0 0;background-color:#fafafa;background-image:-webkit-gradient(linear,0 0,0 100%,from(#fefefe),color-stop(25%,#fefefe),to(#e4e6e9));background-image:-webkit-linear-gradient(#fefefe,#fefefe 25%,#e4e6e9);background-image:-moz-linear-gradient(top,#fefefe,#fefefe 25%,#e4e6e9);background-image:-ms-linear-gradient(#fefefe,#fefefe 25%,#e4e6e9);background-image:-o-linear-gradient(#fefefe,#fefefe 25%,#e4e6e9);background-image:linear-gradient(#fefefe,#fefefe 25%,#e4e6e9);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e4e6e9', GradientType=0);border:1px solid #d2d6db;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}
 #header .acces_compte .avatar_nom span{display:block;height:26px;line-height:26px;float:left}
 #header .acces_compte .avatar{width:28px;border-right:1px solid #d2d6db}
-#header .acces_compte .avatar img{display:block;margin:4px auto 0}
-#header .acces_compte .avatar img{vertical-align:middle}
+#header .acces_compte .avatar img{display:block;margin:4px auto 0;vertical-align:middle}
 #header .acces_compte .nom{padding:0 16px;border-right:1px solid #d2d6db;border-left:1px solid #fff}
 #header .acces_compte .fle{width:28px;background:url(/medias/web/img/pictos/fle_bas_noir7x4.png) no-repeat 50% 50%}
 #header .acces_compte ul{position:absolute;right:0;top:29px;width:98%;background:#fff;list-style-type:none;text-align:left;display:none;border:1px solid #d2d6db;border-radius:0 0 3px 3px}
@@ -1188,8 +1186,7 @@ label i{font-style:normal;display:none}
 .col_droite .bloc_element .ligne_titre{display:block;overflow:hidden;position:relative;border-top:3px solid #16212c;border-bottom:1px solid #eef1f5;border-left:1px solid #eef1f5;background:#e9ecf0}
 .col_droite .bloc_element .titre{float:left;width:238px;padding:8px 16px 6px;border-right:1px solid #fff;background:#fafbfc}
 .col_droite .bloc_element .element:hover .titre{background:#e9ecf0}
-.col_droite .bloc_element .fleche{display:block;float:right;width:42px;border-left:1px solid #e4e6e9}
-.col_droite .bloc_element .fleche{position:absolute;right:13px;top:33%;background:url(/medias/web/img/sprites/icos_petites.png) no-repeat -1px -108px;width:13px;height:22px}
+.col_droite .bloc_element .fleche{display:block;float:right;border-left:1px solid #e4e6e9;position:absolute;right:13px;top:33%;background:url(/medias/web/img/sprites/icos_petites.png) no-repeat -1px -108px;width:13px;height:22px}
 .col_droite .bloc_element .element:hover .fleche{background-position:-15px -108px}
 .contenu_bloc_droit{padding:7px 16px 10px;overflow:hidden}
 .contenu_bloc_droit .liste_chevron li{padding:8px 0 6px}
@@ -1878,8 +1875,7 @@ body.auth-unlogged #core-liberation .form-monlibe-unlogged form{opacity:.3;-ms-f
 #core-liberation ul.list-items li.arround h5,#core-liberation ul.list-items li.arround h4,#core-liberation ul.list-items li.arround p{margin-left:0}
 #core-liberation ul.list-items li.chat p{display:block;clear:both;margin-left:0}
 #core-liberation ul.list-items li.chat h5{margin-bottom:5px}
-#core-liberation ul.list-items-mosts li{margin-bottom:5px}
-#core-liberation ul.list-items-mosts li{font-family:Georgia,"Times New Roman",Times,serif}
+#core-liberation ul.list-items-mosts li{margin-bottom:5px;font-family:Georgia,"Times New Roman",Times,serif}
 #core-liberation ul.list-rss-stream{list-style:none}
 #core-liberation ul.list-rss-stream li{list-style:none;margin-bottom:20px}
 #core-liberation ul.list-rss-stream li h5{margin-bottom:7px;font-weight:400;text-decoration:underline}
@@ -2318,8 +2314,7 @@ form .btn-rounded-degraded input[type=button]:hover,form .btn-rounded-degraded i
 #core-liberation .bg-sprites-icons .arrow-grey-l,#core-liberation .bg-sprites-icons .arrow-grey-b{display:block;background-image:url(http://s0.libe.com/libe/img/common/_sprites_icons/icons.png?9914d0d70a49);background-repeat:no-repeat}
 #core-liberation .bg-sprites-icons .arrow-grey-l{background-position:0 -66px;width:6px;height:8px}
 #core-liberation .bg-sprites-icons .arrow-grey-b{background-position:0 -75px;width:8px;height:7px}
-#core-liberation .bg-sprites-icons .community-bubble{display:block;background-image:url(http://s0.libe.com/libe/img/common/_sprites_icons/icons.png?9914d0d70a49);background-repeat:no-repeat}
-#core-liberation .bg-sprites-icons .community-bubble{background-position:0 -36px;width:21px;height:18px}
+#core-liberation .bg-sprites-icons .community-bubble{display:block;background-image:url(http://s0.libe.com/libe/img/common/_sprites_icons/icons.png?9914d0d70a49);background-repeat:no-repeat;background-position:0 -36px;width:21px;height:18px}
 .site-liberation .toolbox{border-top:1px solid;border-bottom:1px solid;display:block;height:30px;letter-spacing:-1px}
 .site-liberation .toolbox li{float:left;display:block;margin:0 4px;height:30px}
 #core-liberation .toolbox>li:first-child{margin-left:0}
@@ -2379,8 +2374,7 @@ body.init-bar-is-closed #bar-liberation{height:15px}
 #bar-liberation .content .login a.subscribe{position:absolute;display:block;top:10px;right:230px;padding:3px 10px;border-radius:5px;-moz-border-radius:5px;-webkit-border-radius:5px}
 #bar-liberation .content .login a.subscribe:hover{text-decoration:none}
 #bar-liberation .content .login span{position:absolute;display:block;top:13px;right:205px}
-#bar-liberation .content .login a.connect{position:absolute;display:block;top:13px;right:120px}
-#bar-liberation .content .login a.connect{font-weight:700}
+#bar-liberation .content .login a.connect{position:absolute;display:block;top:13px;right:120px;font-weight:700}
 #bar-liberation #login-box-content{display:none;position:absolute;border-left:1px solid;border-right:1px solid;border-bottom:1px solid;top:40px;right:0;z-index:10025;width:184px;padding:10px}
 #bar-liberation #login-box-content form ul li{margin-bottom:10px;clear:both}
 #bar-liberation #login-box-content form label{display:block;float:left;width:150px;margin-bottom:3px}
index 6736bae..b8bde26 100644 (file)
@@ -637,12 +637,12 @@ vows.describe('clean-units').addBatch({
       'a{background:url(/images/blank.png) 0 0 no-repeat}'
     ],
     'strip more': [
-      'a{background:url("/images/blank.png") 0 0 no-repeat}a{display:block}a{background:url("/images/blank2.png") 0 0 no-repeat}',
-      'a{background:url(/images/blank.png) 0 0 no-repeat}a{display:block}a{background:url(/images/blank2.png) 0 0 no-repeat}'
+      'a{background:url("/images/blank.png") 0 0 no-repeat}b{display:block}a{background:url("/images/blank2.png") 0 0 no-repeat}',
+      'a{background:url(/images/blank.png) 0 0 no-repeat}b{display:block}a{background:url(/images/blank2.png) 0 0 no-repeat}'
     ],
     'not strip comments if spaces inside': [
-      'a{background:url("/images/long image name.png") 0 0 no-repeat}a{display:block}a{background:url("/images/no-spaces.png") 0 0 no-repeat}',
-      'a{background:url("/images/long image name.png") 0 0 no-repeat}a{display:block}a{background:url(/images/no-spaces.png) 0 0 no-repeat}'
+      'a{background:url("/images/long image name.png") 0 0 no-repeat}b{display:block}a{background:url("/images/no-spaces.png") 0 0 no-repeat}',
+      'a{background:url("/images/long image name.png") 0 0 no-repeat}b{display:block}a{background:url(/images/no-spaces.png) 0 0 no-repeat}'
     ],
     'not add a space before url\'s hash': "a{background:url(/fonts/d90b3358-e1e2-4abb-ba96-356983a54c22.svg#d90b3358-e1e2-4abb-ba96-356983a54c22)}",
     'keep urls from being stripped down #1': 'a{background:url(/image-1.0.png)}',
@@ -1065,7 +1065,7 @@ title']{display:block}",
     ],
     'of two successive selectors with different body': [
       'a{color:red}a{display:block}',
-      'a{color:red}a{display:block}'
+      'a{color:red;display:block}'
     ],
     'of many successive selectors': [
       'a{color:red}a{color:red}a{color:red}a{color:red}',
@@ -1110,5 +1110,32 @@ title']{display:block}",
       'a{display:inline-block;color:red;font-weight:bolder;font-weight:700;display:block!important;color:#fff}',
       'a{font-weight:bolder;font-weight:700;display:block!important;color:#fff}'
     ]
+  }),
+  'same selectors': cssContext({
+    'of two non-adjacent selectors': '.one{color:red}.two{color:#00f}.one{font-weight:700}',
+    'of two adjacent single selectors': [
+      '.one{color:red}.one{font-weight:700}',
+      '.one{color:red;font-weight:700}'
+    ],
+    'of three adjacent single selectors': [
+      '.one{color:red}.one{font-weight:700}.one{font-size:12px}',
+      '.one{color:red;font-weight:700;font-size:12px}'
+    ],
+    'of two adjacent single, complex selectors': [
+      '#box>.one{color:red}#box>.one{font-weight:700}',
+      '#box>.one{color:red;font-weight:700}'
+    ],
+    'of two adjacent multiple, complex selectors': [
+      '.zero,#box>.one{color:red}.zero,#box>.one{font-weight:700}',
+      '.zero,#box>.one{color:red;font-weight:700}'
+    ],
+    'of two adjacent selectors with duplicate properties #1': [
+      '.one{color:red}.one{color:#fff}',
+      '.one{color:#fff}'
+    ],
+    'of two adjacent selectors with duplicate properties #2': [
+      '.one{color:red;font-weight:bold}.one{color:#fff;font-weight:400}',
+      '.one{color:#fff;font-weight:400}'
+    ]
   })
 }).export(module);