Refactors property merging into a separate Optimizer singleton.
authorGoalSmashers <jakub@goalsmashers.com>
Wed, 30 Oct 2013 19:57:49 +0000 (20:57 +0100)
committerGoalSmashers <jakub@goalsmashers.com>
Sun, 3 Nov 2013 08:49:23 +0000 (09:49 +0100)
lib/properties/optimizer.js [new file with mode: 0644]
lib/selectors/optimizer.js

diff --git a/lib/properties/optimizer.js b/lib/properties/optimizer.js
new file mode 100644 (file)
index 0000000..2b1bc55
--- /dev/null
@@ -0,0 +1,65 @@
+module.exports = function Optimizer() {
+  var tokenize = function(body) {
+    var tokens = body.split(';');
+    var keyValues = [];
+
+    for (var i = 0, l = tokens.length; i < l; i++) {
+      var token = tokens[i];
+      var firstColon = token.indexOf(':');
+      keyValues.push([token.substring(0, firstColon), token.substring(firstColon + 1)]);
+    }
+
+    return keyValues;
+  };
+
+  var optimize = function(tokenized, allowAdjacent) {
+    var merged = [];
+    var properties = [];
+    var lastProperty = null;
+
+    for (var i = 0, l = tokenized.length; i < l; i++) {
+      var property = tokenized[i][0];
+      var value = tokenized[i][1];
+      var alreadyIn = properties.indexOf(property);
+
+      if (alreadyIn > -1 && merged[alreadyIn][1].indexOf('!important') > 0 && value.indexOf('!important') == -1)
+        continue;
+
+      // comment is necessary - we assume that if two properties are one after another
+      // then it is intentional way of redefining property which may not be widely supported
+      // however if `allowAdjacent` is set then the rule does not apply (see merging two adjacent selectors)
+      if (alreadyIn > -1 && (allowAdjacent || lastProperty != property)) {
+        merged.splice(alreadyIn, 1);
+        properties.splice(alreadyIn, 1);
+      }
+
+      merged.push([property, value]);
+      properties.push(property);
+
+      lastProperty = property;
+    }
+
+    return merged;
+  };
+
+  var rebuild = function(tokenized) {
+    var flat = [];
+
+    for (var i = 0, l = tokenized.length; i < l; i++) {
+      flat.push(tokenized[i][0] + ':' + tokenized[i][1]);
+    }
+
+    return flat.join(';');
+  };
+
+  return {
+    process: function(body, allowAdjacent) {
+      var tokenized = tokenize(body);
+      if (tokenized.length < 2)
+        return body;
+
+      var optimized = optimize(tokenized, allowAdjacent);
+      return rebuild(optimized);
+    }
+  };
+};
index 621f442..11145bc 100644 (file)
@@ -1,4 +1,5 @@
 var Tokenizer = require('./tokenizer');
+var PropertyOptimizer = require('../properties/optimizer');
 
 module.exports = function Optimizer(data, options) {
   var specialSelectors = {
@@ -6,6 +7,8 @@ module.exports = function Optimizer(data, options) {
     'ie8': /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:not|:target|:visited|:empty|:first\-of|:last|:nth|:only|:root)/
   };
 
+  var propertyOptimizer = new PropertyOptimizer();
+
   var cleanUpSelector = function(selectors) {
     var plain = [];
     selectors = selectors.split(',');
@@ -24,46 +27,6 @@ module.exports = function Optimizer(data, options) {
     return specialSelectors[options.selectorsMergeMode || '*'].test(selector);
   };
 
-  var mergeProperties = function(body, allowAdjacent) {
-    var merged = [];
-    var properties = [];
-    var flat = [];
-    var tokenized = body.split(';');
-    var lastKey = null;
-
-    if (tokenized.length == 1 && tokenized[0] === '')
-      return body;
-
-    for (var i = 0, l = tokenized.length; i < l; i++) {
-      var firstColon = tokenized[i].indexOf(':');
-      var key = tokenized[i].substring(0, firstColon);
-      var value = tokenized[i].substring(firstColon + 1);
-      var alreadyIn = properties.indexOf(key);
-
-      if (alreadyIn > -1 && merged[alreadyIn][1].indexOf('!important') > 0 && value.indexOf('!important') == -1)
-        continue;
-
-      // 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
-      // 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);
-      }
-
-      merged.push([key, value]);
-      properties.push(key);
-
-      lastKey = key;
-    }
-
-    for (var j = 0, m = merged.length; j < m; j++) {
-      flat.push(merged[j].join(':'));
-    }
-
-    return flat.join(';');
-  };
-
   var removeDuplicates = function(tokens) {
     var matched = {};
     var forRemoval = [];
@@ -102,7 +65,7 @@ module.exports = function Optimizer(data, options) {
         continue;
 
       if (token.selector == lastToken.selector) {
-        lastToken.body = mergeProperties(lastToken.body + ';' + token.body, true);
+        lastToken.body = propertyOptimizer.process(lastToken.body + ';' + token.body, true);
         forRemoval.push(i);
       } else if (token.body == lastToken.body && !isSpecial(token.selector) && !isSpecial(lastToken.selector)) {
         lastToken.selector = cleanUpSelector(lastToken.selector + ',' + token.selector);
@@ -124,7 +87,7 @@ module.exports = function Optimizer(data, options) {
 
       if (token.selector) {
         token.selector = cleanUpSelector(token.selector);
-        token.body = mergeProperties(token.body, false);
+        token.body = propertyOptimizer.process(token.body, false);
       } else if (token.block) {
         optimize(token.body);
       }