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
 | 
						|
    });
 | 
						|
};
 |