178 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			178 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| var List = require('css-tree').List;
 | |
| var walk = require('css-tree').walk;
 | |
| var utils = require('./utils');
 | |
| 
 | |
| function calcSelectorLength(list) {
 | |
|     var length = 0;
 | |
| 
 | |
|     list.each(function(data) {
 | |
|         length += data.id.length + 1;
 | |
|     });
 | |
| 
 | |
|     return length - 1;
 | |
| }
 | |
| 
 | |
| function calcDeclarationsLength(tokens) {
 | |
|     var length = 0;
 | |
| 
 | |
|     for (var i = 0; i < tokens.length; i++) {
 | |
|         length += tokens[i].length;
 | |
|     }
 | |
| 
 | |
|     return (
 | |
|         length +          // declarations
 | |
|         tokens.length - 1 // delimeters
 | |
|     );
 | |
| }
 | |
| 
 | |
| function processRule(node, item, list) {
 | |
|     var avoidRulesMerge = this.block !== null ? this.block.avoidRulesMerge : false;
 | |
|     var selectors = node.prelude.children;
 | |
|     var block = node.block;
 | |
|     var disallowDownMarkers = Object.create(null);
 | |
|     var allowMergeUp = true;
 | |
|     var allowMergeDown = true;
 | |
| 
 | |
|     list.prevUntil(item.prev, function(prev, prevItem) {
 | |
|         var prevBlock = prev.block;
 | |
|         var prevType = prev.type;
 | |
| 
 | |
|         if (prevType !== 'Rule') {
 | |
|             var unsafe = utils.unsafeToSkipNode.call(selectors, prev);
 | |
| 
 | |
|             if (!unsafe && prevType === 'Atrule' && prevBlock) {
 | |
|                 walk(prevBlock, {
 | |
|                     visit: 'Rule',
 | |
|                     enter: function(node) {
 | |
|                         node.prelude.children.each(function(data) {
 | |
|                             disallowDownMarkers[data.compareMarker] = true;
 | |
|                         });
 | |
|                     }
 | |
|                 });
 | |
|             }
 | |
| 
 | |
|             return unsafe;
 | |
|         }
 | |
| 
 | |
|         var prevSelectors = prev.prelude.children;
 | |
| 
 | |
|         if (node.pseudoSignature !== prev.pseudoSignature) {
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         allowMergeDown = !prevSelectors.some(function(selector) {
 | |
|             return selector.compareMarker in disallowDownMarkers;
 | |
|         });
 | |
| 
 | |
|         // try prev ruleset if simpleselectors has no equal specifity and element selector
 | |
|         if (!allowMergeDown && !allowMergeUp) {
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         // try to join by selectors
 | |
|         if (allowMergeUp && utils.isEqualSelectors(prevSelectors, selectors)) {
 | |
|             prevBlock.children.appendList(block.children);
 | |
|             list.remove(item);
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         // try to join by properties
 | |
|         var diff = utils.compareDeclarations(block.children, prevBlock.children);
 | |
| 
 | |
|         // console.log(diff.eq, diff.ne1, diff.ne2);
 | |
| 
 | |
|         if (diff.eq.length) {
 | |
|             if (!diff.ne1.length && !diff.ne2.length) {
 | |
|                 // equal blocks
 | |
|                 if (allowMergeDown) {
 | |
|                     utils.addSelectors(selectors, prevSelectors);
 | |
|                     list.remove(prevItem);
 | |
|                 }
 | |
| 
 | |
|                 return true;
 | |
|             } else if (!avoidRulesMerge) { /* probably we don't need to prevent those merges for @keyframes
 | |
|                                               TODO: need to be checked */
 | |
| 
 | |
|                 if (diff.ne1.length && !diff.ne2.length) {
 | |
|                     // prevBlock is subset block
 | |
|                     var selectorLength = calcSelectorLength(selectors);
 | |
|                     var blockLength = calcDeclarationsLength(diff.eq); // declarations length
 | |
| 
 | |
|                     if (allowMergeUp && selectorLength < blockLength) {
 | |
|                         utils.addSelectors(prevSelectors, selectors);
 | |
|                         block.children = new List().fromArray(diff.ne1);
 | |
|                     }
 | |
|                 } else if (!diff.ne1.length && diff.ne2.length) {
 | |
|                     // node is subset of prevBlock
 | |
|                     var selectorLength = calcSelectorLength(prevSelectors);
 | |
|                     var blockLength = calcDeclarationsLength(diff.eq); // declarations length
 | |
| 
 | |
|                     if (allowMergeDown && selectorLength < blockLength) {
 | |
|                         utils.addSelectors(selectors, prevSelectors);
 | |
|                         prevBlock.children = new List().fromArray(diff.ne2);
 | |
|                     }
 | |
|                 } else {
 | |
|                     // diff.ne1.length && diff.ne2.length
 | |
|                     // extract equal block
 | |
|                     var newSelector = {
 | |
|                         type: 'SelectorList',
 | |
|                         loc: null,
 | |
|                         children: utils.addSelectors(prevSelectors.copy(), selectors)
 | |
|                     };
 | |
|                     var newBlockLength = calcSelectorLength(newSelector.children) + 2; // selectors length + curly braces length
 | |
|                     var blockLength = calcDeclarationsLength(diff.eq); // declarations length
 | |
| 
 | |
|                     // create new ruleset if declarations length greater than
 | |
|                     // ruleset description overhead
 | |
|                     if (blockLength >= newBlockLength) {
 | |
|                         var newItem = list.createItem({
 | |
|                             type: 'Rule',
 | |
|                             loc: null,
 | |
|                             prelude: newSelector,
 | |
|                             block: {
 | |
|                                 type: 'Block',
 | |
|                                 loc: null,
 | |
|                                 children: new List().fromArray(diff.eq)
 | |
|                             },
 | |
|                             pseudoSignature: node.pseudoSignature
 | |
|                         });
 | |
| 
 | |
|                         block.children = new List().fromArray(diff.ne1);
 | |
|                         prevBlock.children = new List().fromArray(diff.ne2overrided);
 | |
| 
 | |
|                         if (allowMergeUp) {
 | |
|                             list.insert(newItem, prevItem);
 | |
|                         } else {
 | |
|                             list.insert(newItem, item);
 | |
|                         }
 | |
| 
 | |
|                         return true;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (allowMergeUp) {
 | |
|             // TODO: disallow up merge only if any property interception only (i.e. diff.ne2overrided.length > 0);
 | |
|             // await property families to find property interception correctly
 | |
|             allowMergeUp = !prevSelectors.some(function(prevSelector) {
 | |
|                 return selectors.some(function(selector) {
 | |
|                     return selector.compareMarker === prevSelector.compareMarker;
 | |
|                 });
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         prevSelectors.each(function(data) {
 | |
|             disallowDownMarkers[data.compareMarker] = true;
 | |
|         });
 | |
|     });
 | |
| }
 | |
| 
 | |
| module.exports = function restructRule(ast) {
 | |
|     walk(ast, {
 | |
|         visit: 'Rule',
 | |
|         reverse: true,
 | |
|         enter: processRule
 | |
|     });
 | |
| };
 |