Adds better non-adjacent selector merging when body is the same.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 6 Jan 2015 19:16:57 +0000 (19:16 +0000)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 6 Jan 2015 20:27:11 +0000 (20:27 +0000)
* See #158.

History.md
lib/selectors/optimizers/advanced.js
test/fixtures/big-min.css
test/fixtures/blueprint-min.css
test/integration-test.js

index c8a8462..b5582fc 100644 (file)
@@ -2,6 +2,7 @@
 ==================
 
 * Adds 0deg to 0 minification where possible.
+* Adds better non-adjacent selector merging when body is the same.
 * Fixed issue [#182](https://github.com/GoalSmashers/clean-css/issues/182) - removing space after closing brace.
 
 [3.0.2 / 2015-01-04](https://github.com/jakubpawlowicz/clean-css/compare/v3.0.1...v3.0.2)
index 6c3175c..5b71569 100644 (file)
@@ -19,6 +19,10 @@ function changeSelectorOf(token, newSelectors) {
   token.metadata.selectorsList = newSelectors.list;
 }
 
+function unsafeSelector(value) {
+  return /\.|\*| :/.test(value);
+}
+
 AdvancedOptimizer.prototype.isSpecial = function (selector) {
   return this.options.compatibility.selectors.special.test(selector);
 };
@@ -255,6 +259,32 @@ AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, o
   }
 };
 
+AdvancedOptimizer.prototype.mergeNonAdjacentByBody = function (tokens) {
+  var candidates = {};
+
+  for (var i = tokens.length - 1; i >= 0; i--) {
+    var token = tokens[i];
+    if (token.kind != 'selector')
+      continue;
+
+    if (token.body.length > 0 && unsafeSelector(token.metadata.selector))
+      candidates = {};
+
+    var oldToken = candidates[token.metadata.body];
+    if (oldToken && !this.isSpecial(token.metadata.selector) && !this.isSpecial(oldToken.metadata.selector)) {
+      changeSelectorOf(
+        token,
+        CleanUp.selectors(oldToken.value.concat(token.value), false)
+      );
+
+      oldToken.body = [];
+      candidates[token.metadata.body] = null;
+    }
+
+    candidates[token.metadata.body] = token;
+  }
+};
+
 function optimizeProperties(tokens, propertyOptimizer) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
@@ -287,6 +317,8 @@ AdvancedOptimizer.prototype.optimize = function (tokens) {
 
     self.removeDuplicates(tokens);
     self.mergeAdjacent(tokens);
+
+    self.mergeNonAdjacentByBody(tokens);
   }
 
   _optimize(tokens);
index d301a64..fc8116b 100644 (file)
@@ -4,7 +4,7 @@ audio,canvas,video{display:inline-block}
 [hidden],audio:not([controls]){display:none}
 html{-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}
 button,html,input,select,textarea{font-family:sans-serif}
-body{margin:0}
+body,figure,form{margin:0}
 a:focus{outline:dotted thin}
 a:active,a:hover{outline:0}
 h1,h2,h3,h4,h5,h6{margin:0;font-weight:700}
@@ -27,7 +27,6 @@ dd{margin:0 0 0 40px}
 nav ol,nav ul{list-style:none}
 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}
 legend{border:0;padding:0;white-space:normal}
 button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline}
index fc00f14..3651f8f 100644 (file)
@@ -27,7 +27,7 @@ p .right{margin:1.5em 0 1.5em 1.5em;padding:0}
 a:focus,a:hover{color:#09f}
 a{color:#06c;text-decoration:underline}
 blockquote{margin:1.5em;color:#666;font-style:italic}
-dfn,strong{font-weight:700}
+dfn,dl dt,strong,th{font-weight:700}
 dfn,em{font-style:italic}
 sub,sup{line-height:0}
 abbr,acronym{border-bottom:1px dotted #666}
@@ -40,10 +40,8 @@ ol,ul{margin:0 1.5em 1.5em 0;padding-left:1.5em}
 ul{list-style-type:disc}
 ol{list-style-type:decimal}
 dl{margin:0 0 1.5em}
-dl dt{font-weight:700}
 dd{margin-left:1.5em}
 table{margin-bottom:1.4em;width:100%}
-th{font-weight:700}
 thead th{background:#c3d9ff}
 caption,td,th{padding:4px 10px 4px 5px}
 tbody tr.even td,tbody tr:nth-child(even) td{background:#e5ecf9}
index 568385f..4886c50 100644 (file)
@@ -1784,6 +1784,22 @@ title']{display:block}",
     'with repeated selectors': [
       '#zero>p,.one,.two{color:red}.two,#zero>p,.three{color:red}',
       '#zero>p,.one,.three,.two{color:red}'
+    ],
+    'of element selectors': [
+      'p{color:red}a{color:#000}div{color:red}',
+      'div,p{color:red}a{color:#000}'
+    ],
+    'of element selectors inside @media': [
+      '@media screen{p{color:red}a{color:#000}div{color:red}}',
+      '@media screen{div,p{color:red}a{color:#000}}'
+    ],
+    'of element selectors with a class selector in between': [
+      'p{color:red}.a{color:#000}div{color:red}',
+      'p{color:red}.a{color:#000}div{color:red}'
+    ],
+    'of element selectors with an empty class selector in between': [
+      'p{color:red}.a{}div{color:red}',
+      'div,p{color:red}'
     ]
   }),
   'same bodies - IE8 compat': cssContext({