Extracts 'merge media queries' optimization into a module.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 21 Jun 2015 14:03:44 +0000 (15:03 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 21 Jun 2015 14:03:44 +0000 (15:03 +0100)
lib/selectors/advanced.js
lib/selectors/merge-media-queries.js [new file with mode: 0644]
test/selectors/merge-media-queries-test.js [new file with mode: 0644]

index 6487106..48e55fe 100644 (file)
@@ -1,8 +1,5 @@
 var optimizeProperties = require('../properties/optimizer');
 
-var extractProperties = require('./extractor');
-var canReorder = require('./reorderable').canReorder;
-
 var removeDuplicates = require('./remove-duplicates');
 var mergeAdjacent = require('./merge-adjacent');
 var reduceNonAdjacent = require('./reduce-non-adjacent');
@@ -10,57 +7,13 @@ var mergeNonAdjacentBySelector = require('./merge-non-adjacent-by-selector');
 var mergeNonAdjacentByBody = require('./merge-non-adjacent-by-body');
 var restructure = require('./restructure');
 var removeDuplicateMediaQueries = require('./remove-duplicate-media-queries');
+var mergeMediaQueries = require('./merge-media-queries');
 
 function AdvancedOptimizer(options, context) {
   this.options = options;
   this.validator = context.validator;
 }
 
-AdvancedOptimizer.prototype.mergeMediaQueries = function (tokens) {
-  var candidates = {};
-  var reduced = [];
-
-  for (var i = tokens.length - 1; i >= 0; i--) {
-    var token = tokens[i];
-    if (token[0] != 'block')
-      continue;
-
-    var candidate = candidates[token[1][0]];
-    if (!candidate) {
-      candidate = [];
-      candidates[token[1][0]] = candidate;
-    }
-
-    candidate.push(i);
-  }
-
-  for (var name in candidates) {
-    var positions = candidates[name];
-
-    positionLoop:
-    for (var j = positions.length - 1; j > 0; j--) {
-      var source = tokens[positions[j]];
-      var target = tokens[positions[j - 1]];
-      var movedProperties = extractProperties(source);
-
-      for (var k = positions[j] + 1; k < positions[j - 1]; k++) {
-        var traversedProperties = extractProperties(tokens[k]);
-
-        // moved then traversed as we move @media towards the end
-        if (!canReorder(movedProperties, traversedProperties))
-          continue positionLoop;
-      }
-
-      target[2] = source[2].concat(target[2]);
-      source[2] = [];
-
-      reduced.push(target);
-    }
-  }
-
-  return reduced;
-};
-
 AdvancedOptimizer.prototype.removeEmpty = function (tokens) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
@@ -124,7 +77,7 @@ AdvancedOptimizer.prototype.optimize = function (tokens) {
 
     if (self.options.mediaMerging) {
       removeDuplicateMediaQueries(tokens);
-      var reduced = self.mergeMediaQueries(tokens);
+      var reduced = mergeMediaQueries(tokens);
       for (var i = reduced.length - 1; i >= 0; i--) {
         _optimize(reduced[i][2]);
       }
diff --git a/lib/selectors/merge-media-queries.js b/lib/selectors/merge-media-queries.js
new file mode 100644 (file)
index 0000000..89ea572
--- /dev/null
@@ -0,0 +1,49 @@
+var canReorder = require('./reorderable').canReorder;
+var extractProperties = require('./extractor');
+
+function mergeMediaQueries(tokens) {
+  var candidates = {};
+  var reduced = [];
+
+  for (var i = tokens.length - 1; i >= 0; i--) {
+    var token = tokens[i];
+    if (token[0] != 'block')
+      continue;
+
+    var candidate = candidates[token[1][0]];
+    if (!candidate) {
+      candidate = [];
+      candidates[token[1][0]] = candidate;
+    }
+
+    candidate.push(i);
+  }
+
+  for (var name in candidates) {
+    var positions = candidates[name];
+
+    positionLoop:
+    for (var j = positions.length - 1; j > 0; j--) {
+      var source = tokens[positions[j]];
+      var target = tokens[positions[j - 1]];
+      var movedProperties = extractProperties(source);
+
+      for (var k = positions[j] + 1; k < positions[j - 1]; k++) {
+        var traversedProperties = extractProperties(tokens[k]);
+
+        // moved then traversed as we move @media towards the end
+        if (!canReorder(movedProperties, traversedProperties))
+          continue positionLoop;
+      }
+
+      target[2] = source[2].concat(target[2]);
+      source[2] = [];
+
+      reduced.push(target);
+    }
+  }
+
+  return reduced;
+}
+
+module.exports = mergeMediaQueries;
diff --git a/test/selectors/merge-media-queries-test.js b/test/selectors/merge-media-queries-test.js
new file mode 100644 (file)
index 0000000..86a3bb2
--- /dev/null
@@ -0,0 +1,93 @@
+var vows = require('vows');
+var optimizerContext = require('../test-helper').optimizerContext;
+
+vows.describe('merge media queries')
+  .addBatch(
+    optimizerContext('different ones', {
+      'different ones': [
+        '@media screen{a{color:red}}@media print{div{display:block}}',
+        '@media screen{a{color:red}}@media print{div{display:block}}'
+      ],
+      'other than @media': [
+        '@font-face{font-family:A}@font-face{font-family:B}',
+        '@font-face{font-family:A}@font-face{font-family:B}'
+      ],
+      'with empty selector': [
+        '@media screen{a{color:red}div{}}',
+        '@media screen{a{color:red}}'
+      ]
+    })
+  )
+  .addBatch(
+    optimizerContext('adjacent', {
+      'same two adjacent': [
+        '@media screen{a{color:red}}@media screen{div{display:block}}',
+        '@media screen{a{color:red}div{display:block}}'
+      ],
+      'same three adjacent': [
+        '@media screen{a{color:red}}@media screen{div{display:block}}@media screen{body{width:100%}}',
+        '@media screen{a{color:red}div{display:block}body{width:100%}}'
+      ],
+      'same two with selectors in between': [
+        '@media screen{a{color:red}}body{width:100%}.one{height:100px}@media screen{div{display:block}}',
+        'body{width:100%}.one{height:100px}@media screen{a{color:red}div{display:block}}'
+      ],
+      'same two with other @media in between': [
+        '@media screen{a{color:red}}@media (min-width:1024px){body{width:100%}}@media screen{div{display:block}}',
+        '@media (min-width:1024px){body{width:100%}}@media screen{a{color:red}div{display:block}}'
+      ],
+      'same two with breaking properties in between': [
+        '@media screen{a{color:red}}.one{color:#00f}@media screen{div{display:block}}',
+        '@media screen{a{color:red}}.one{color:#00f}@media screen{div{display:block}}'
+      ],
+      'same two with breaking @media in between': [
+        '@media screen{a{color:red}}@media (min-width:1024px){.one{color:#00f}}@media screen{div{display:block}}',
+        '@media screen{a{color:red}}@media (min-width:1024px){.one{color:#00f}}@media screen{div{display:block}}'
+      ],
+      'same two with breaking nested @media in between': [
+        '@media screen{a{color:red}}@media (min-width:1024px){@media screen{.one{color:#00f}}}@media screen{div{display:block}}',
+        '@media screen{a{color:red}}@media (min-width:1024px){@media screen{.one{color:#00f}}}@media screen{div{display:block}}'
+      ],
+      'intermixed': [
+        '@media screen{a{color:red}}@media (min-width:1024px){p{width:100%}}@media screen{div{display:block}}@media (min-width:1024px){body{height:100%}}',
+        '@media screen{a{color:red}div{display:block}}@media (min-width:1024px){p{width:100%}body{height:100%}}'
+      ],
+      'same two with overriding shorthand in between': [
+        '@media screen{a{font-size:10px}}@media (min-width:1024px){.one{font:12px Helvetica}}@media screen{div{display:block}}',
+        '@media screen{a{font-size:10px}}@media (min-width:1024px){.one{font:12px Helvetica}}@media screen{div{display:block}}'
+      ],
+      'same two with different component property in between': [
+        '@media screen{a{font-size:10px}}@media (min-width:1024px){.one{font-weight:700}}@media screen{div{display:block}}',
+        '@media (min-width:1024px){.one{font-weight:700}}@media screen{a{font-size:10px}div{display:block}}'
+      ],
+      'same two with same values as moved in between': [
+        '@media screen{a{color:red}}@media (min-width:1024px){.one{color:red}}@media screen{div{display:block}}',
+        '@media (min-width:1024px){.one{color:red}}@media screen{a{color:red}div{display:block}}'
+      ],
+      'further optimizations': [
+        '@media screen{a{color:red}}@media screen{a{display:block}}',
+        '@media screen{a{color:red;display:block}}'
+      ],
+      'with comments': [
+        '@media screen{a{color:red}}/*! a comment */@media screen{a{display:block}}',
+        '/*! a comment */@media screen{a{color:red;display:block}}'
+      ]
+    })
+  )
+  .addBatch(
+    optimizerContext('advanced off', {
+      'keeps content same': [
+        '@media screen{a{color:red}}@media screen{a{display:block}}',
+        '@media screen{a{color:red}}@media screen{a{display:block}}'
+      ]
+    }, { advanced: false })
+  )
+  .addBatch(
+    optimizerContext('media merging off', {
+      'keeps content same': [
+        '@media screen{a{color:red}}@media screen{a{display:block}}',
+        '@media screen{a{color:red}}@media screen{a{display:block}}'
+      ]
+    }, { mediaMerging: false })
+  )
+  .export(module);