Adds basic optimizer removing duplicate selectors from a list.
authorGoalSmashers <jakub@goalsmashers.com>
Sun, 29 Sep 2013 10:32:05 +0000 (12:32 +0200)
committerGoalSmashers <jakub@goalsmashers.com>
Sat, 2 Nov 2013 16:30:37 +0000 (17:30 +0100)
History.md
lib/clean.js
lib/selectors/optimizer.js [new file with mode: 0644]
lib/selectors/tokenizer.js
test/unit-test.js

index d07d5a9..6c48a01 100644 (file)
@@ -8,6 +8,7 @@
 * Fixed issue [#159](https://github.com/GoalSmashers/clean-css/issues/159) - escaped quotes inside content.
 * Fixed issue [#162](https://github.com/GoalSmashers/clean-css/issues/162) - strip quotes from base64 encoded URLs.
 * 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.
 
 1.1.7 / 2013-10-28
 ==================
index a3e1cbc..0ab6eea 100644 (file)
@@ -20,7 +20,7 @@ var ExpressionsProcessor = require('./text/expressions');
 var FreeTextProcessor = require('./text/free');
 var UrlsProcessor = require('./text/urls');
 
-var SelectorsTokenizer = require('./selectors/tokenizer');
+var SelectorsOptimizer = require('./selectors/optimizer');
 
 var CleanCSS = {
   process: function(data, options) {
@@ -248,7 +248,7 @@ var CleanCSS = {
     });
 
     replace(function optimizeSelectors() {
-      data = new SelectorsTokenizer(data).process();
+      data = new SelectorsOptimizer(data).process();
     });
 
     replace(function restoreUrls() {
diff --git a/lib/selectors/optimizer.js b/lib/selectors/optimizer.js
new file mode 100644 (file)
index 0000000..cacef24
--- /dev/null
@@ -0,0 +1,51 @@
+var Tokenizer = require('./tokenizer');
+
+module.exports = function Optimizer(data) {
+  var stripRepeats = function(selectors) {
+    var plain = [];
+    selectors = selectors.split(',');
+
+    for (var i = 0, l = selectors.length; i < l; i++) {
+      var sel = selectors[i];
+
+      if (plain.indexOf(sel) == -1)
+        plain.push(sel);
+    }
+
+    return plain.join(',');
+  };
+
+  var optimize = function(tokens) {
+    tokens = (Array.isArray(tokens) ? tokens : [tokens]);
+    for (var i = 0, l = tokens.length; i < l; i++) {
+      var token = tokens[i];
+
+      if (token.selector)
+        token.selector = stripRepeats(token.selector);
+      if (token.block)
+        optimize(token.body);
+    }
+  };
+
+  var rebuild = function(tokens) {
+    return (Array.isArray(tokens) ? tokens : [tokens])
+      .map(function(token) {
+        if (typeof token == 'string')
+          return token;
+
+        if (token.block)
+          return token.block + '{' + rebuild(token.body) + '}';
+        else
+          return token.selector + '{' + token.body + '}';
+      })
+      .join('');
+  };
+
+  return {
+    process: function() {
+      var tokenized = new Tokenizer(data).process();
+      optimize(tokenized);
+      return rebuild(tokenized);
+    }
+  };
+};
index 236d436..8fab940 100644 (file)
@@ -106,23 +106,9 @@ module.exports = function Tokenizer(data) {
     return tokenized;
   };
 
-  var rebuild = function(tokens) {
-    return (Array.isArray(tokens) ? tokens : [tokens])
-      .map(function(token) {
-        if (typeof token == 'string')
-          return token;
-
-        if (token.block)
-          return token.block + '{' + rebuild(token.body) + '}';
-        else
-          return token.selector + '{' + token.body + '}';
-      })
-      .join('');
-  };
-
   return {
     process: function() {
-      return rebuild(tokenize());
+      return tokenize();
     }
   };
 };
index 3c7a88b..e95a92c 100644 (file)
@@ -1035,5 +1035,27 @@ title']{display:block}",
       "@import url(/fake.css);",
       "@import url(/fake.css);"
     ]
-  }, { processImport: false })
+  }, { processImport: false }),
+  'duplicate selectors in a list': cssContext({
+    'of a duplicate selector': [
+      'a,a{color:red}',
+      'a{color:red}'
+    ],
+    'of an unordered multiply repeated selector': [
+      'a,b,p,a{color:red}',
+      'a,b,p{color:red}'
+    ],
+    'of an unordered multiply repeated selector within a block': [
+      '@media screen{a,b,p,a{color:red}}',
+      '@media screen{a,b,p{color:red}}'
+    ],
+    'of an unordered multiply repeated complex selector within a block #1': [
+      '@media screen{a,.link[data-path],p,.link[data-path]{color:red}}',
+      '@media screen{a,.link[data-path],p{color:red}}'
+    ],
+    'of an unordered multiply repeated complex selector within a block #2': [
+      '@media screen{a,#foo[data-path^="bar bar"],p,#foo[data-path^="bar bar"]{color:red}}',
+      '@media screen{a,#foo[data-path^="bar bar"],p{color:red}}'
+    ]
+  })
 }).export(module);