You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					297 lines
				
				11 KiB
			
		
		
			
		
	
	
					297 lines
				
				11 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								var resolveProperty = require('css-tree').property;
							 | 
						||
| 
								 | 
							
								var resolveKeyword = require('css-tree').keyword;
							 | 
						||
| 
								 | 
							
								var walk = require('css-tree').walk;
							 | 
						||
| 
								 | 
							
								var generate = require('css-tree').generate;
							 | 
						||
| 
								 | 
							
								var fingerprintId = 1;
							 | 
						||
| 
								 | 
							
								var dontRestructure = {
							 | 
						||
| 
								 | 
							
								    'src': 1 // https://github.com/afelix/csso/issues/50
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var DONT_MIX_VALUE = {
							 | 
						||
| 
								 | 
							
								    // https://developer.mozilla.org/en-US/docs/Web/CSS/display#Browser_compatibility
							 | 
						||
| 
								 | 
							
								    'display': /table|ruby|flex|-(flex)?box$|grid|contents|run-in/i,
							 | 
						||
| 
								 | 
							
								    // https://developer.mozilla.org/en/docs/Web/CSS/text-align
							 | 
						||
| 
								 | 
							
								    'text-align': /^(start|end|match-parent|justify-all)$/i
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var CURSOR_SAFE_VALUE = [
							 | 
						||
| 
								 | 
							
								    'auto', 'crosshair', 'default', 'move', 'text', 'wait', 'help',
							 | 
						||
| 
								 | 
							
								    'n-resize', 'e-resize', 's-resize', 'w-resize',
							 | 
						||
| 
								 | 
							
								    'ne-resize', 'nw-resize', 'se-resize', 'sw-resize',
							 | 
						||
| 
								 | 
							
								    'pointer', 'progress', 'not-allowed', 'no-drop', 'vertical-text', 'all-scroll',
							 | 
						||
| 
								 | 
							
								    'col-resize', 'row-resize'
							 | 
						||
| 
								 | 
							
								];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var POSITION_SAFE_VALUE = [
							 | 
						||
| 
								 | 
							
								    'static', 'relative', 'absolute', 'fixed'
							 | 
						||
| 
								 | 
							
								];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var NEEDLESS_TABLE = {
							 | 
						||
| 
								 | 
							
								    'border-width': ['border'],
							 | 
						||
| 
								 | 
							
								    'border-style': ['border'],
							 | 
						||
| 
								 | 
							
								    'border-color': ['border'],
							 | 
						||
| 
								 | 
							
								    'border-top': ['border'],
							 | 
						||
| 
								 | 
							
								    'border-right': ['border'],
							 | 
						||
| 
								 | 
							
								    'border-bottom': ['border'],
							 | 
						||
| 
								 | 
							
								    'border-left': ['border'],
							 | 
						||
| 
								 | 
							
								    'border-top-width': ['border-top', 'border-width', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-right-width': ['border-right', 'border-width', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-bottom-width': ['border-bottom', 'border-width', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-left-width': ['border-left', 'border-width', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-top-style': ['border-top', 'border-style', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-right-style': ['border-right', 'border-style', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-bottom-style': ['border-bottom', 'border-style', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-left-style': ['border-left', 'border-style', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-top-color': ['border-top', 'border-color', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-right-color': ['border-right', 'border-color', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-bottom-color': ['border-bottom', 'border-color', 'border'],
							 | 
						||
| 
								 | 
							
								    'border-left-color': ['border-left', 'border-color', 'border'],
							 | 
						||
| 
								 | 
							
								    'margin-top': ['margin'],
							 | 
						||
| 
								 | 
							
								    'margin-right': ['margin'],
							 | 
						||
| 
								 | 
							
								    'margin-bottom': ['margin'],
							 | 
						||
| 
								 | 
							
								    'margin-left': ['margin'],
							 | 
						||
| 
								 | 
							
								    'padding-top': ['padding'],
							 | 
						||
| 
								 | 
							
								    'padding-right': ['padding'],
							 | 
						||
| 
								 | 
							
								    'padding-bottom': ['padding'],
							 | 
						||
| 
								 | 
							
								    'padding-left': ['padding'],
							 | 
						||
| 
								 | 
							
								    'font-style': ['font'],
							 | 
						||
| 
								 | 
							
								    'font-variant': ['font'],
							 | 
						||
| 
								 | 
							
								    'font-weight': ['font'],
							 | 
						||
| 
								 | 
							
								    'font-size': ['font'],
							 | 
						||
| 
								 | 
							
								    'font-family': ['font'],
							 | 
						||
| 
								 | 
							
								    'list-style-type': ['list-style'],
							 | 
						||
| 
								 | 
							
								    'list-style-position': ['list-style'],
							 | 
						||
| 
								 | 
							
								    'list-style-image': ['list-style']
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function getPropertyFingerprint(propertyName, declaration, fingerprints) {
							 | 
						||
| 
								 | 
							
								    var realName = resolveProperty(propertyName).basename;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (realName === 'background') {
							 | 
						||
| 
								 | 
							
								        return propertyName + ':' + generate(declaration.value);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var declarationId = declaration.id;
							 | 
						||
| 
								 | 
							
								    var fingerprint = fingerprints[declarationId];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!fingerprint) {
							 | 
						||
| 
								 | 
							
								        switch (declaration.value.type) {
							 | 
						||
| 
								 | 
							
								            case 'Value':
							 | 
						||
| 
								 | 
							
								                var vendorId = '';
							 | 
						||
| 
								 | 
							
								                var iehack = '';
							 | 
						||
| 
								 | 
							
								                var special = {};
							 | 
						||
| 
								 | 
							
								                var raw = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                declaration.value.children.each(function walk(node) {
							 | 
						||
| 
								 | 
							
								                    switch (node.type) {
							 | 
						||
| 
								 | 
							
								                        case 'Value':
							 | 
						||
| 
								 | 
							
								                        case 'Brackets':
							 | 
						||
| 
								 | 
							
								                        case 'Parentheses':
							 | 
						||
| 
								 | 
							
								                            node.children.each(walk);
							 | 
						||
| 
								 | 
							
								                            break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                        case 'Raw':
							 | 
						||
| 
								 | 
							
								                            raw = true;
							 | 
						||
| 
								 | 
							
								                            break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                        case 'Identifier':
							 | 
						||
| 
								 | 
							
								                            var name = node.name;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            if (!vendorId) {
							 | 
						||
| 
								 | 
							
								                                vendorId = resolveKeyword(name).vendor;
							 | 
						||
| 
								 | 
							
								                            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            if (/\\[09]/.test(name)) {
							 | 
						||
| 
								 | 
							
								                                iehack = RegExp.lastMatch;
							 | 
						||
| 
								 | 
							
								                            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            if (realName === 'cursor') {
							 | 
						||
| 
								 | 
							
								                                if (CURSOR_SAFE_VALUE.indexOf(name) === -1) {
							 | 
						||
| 
								 | 
							
								                                    special[name] = true;
							 | 
						||
| 
								 | 
							
								                                }
							 | 
						||
| 
								 | 
							
								                            } else if (realName === 'position') {
							 | 
						||
| 
								 | 
							
								                                if (POSITION_SAFE_VALUE.indexOf(name) === -1) {
							 | 
						||
| 
								 | 
							
								                                    special[name] = true;
							 | 
						||
| 
								 | 
							
								                                }
							 | 
						||
| 
								 | 
							
								                            } else if (DONT_MIX_VALUE.hasOwnProperty(realName)) {
							 | 
						||
| 
								 | 
							
								                                if (DONT_MIX_VALUE[realName].test(name)) {
							 | 
						||
| 
								 | 
							
								                                    special[name] = true;
							 | 
						||
| 
								 | 
							
								                                }
							 | 
						||
| 
								 | 
							
								                            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                        case 'Function':
							 | 
						||
| 
								 | 
							
								                            var name = node.name;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            if (!vendorId) {
							 | 
						||
| 
								 | 
							
								                                vendorId = resolveKeyword(name).vendor;
							 | 
						||
| 
								 | 
							
								                            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            if (name === 'rect') {
							 | 
						||
| 
								 | 
							
								                                // there are 2 forms of rect:
							 | 
						||
| 
								 | 
							
								                                //   rect(<top>, <right>, <bottom>, <left>) - standart
							 | 
						||
| 
								 | 
							
								                                //   rect(<top> <right> <bottom> <left>) – backwards compatible syntax
							 | 
						||
| 
								 | 
							
								                                // only the same form values can be merged
							 | 
						||
| 
								 | 
							
								                                var hasComma = node.children.some(function(node) {
							 | 
						||
| 
								 | 
							
								                                    return node.type === 'Operator' && node.value === ',';
							 | 
						||
| 
								 | 
							
								                                });
							 | 
						||
| 
								 | 
							
								                                if (!hasComma) {
							 | 
						||
| 
								 | 
							
								                                    name = 'rect-backward';
							 | 
						||
| 
								 | 
							
								                                }
							 | 
						||
| 
								 | 
							
								                            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            special[name + '()'] = true;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            // check nested tokens too
							 | 
						||
| 
								 | 
							
								                            node.children.each(walk);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                        case 'Dimension':
							 | 
						||
| 
								 | 
							
								                            var unit = node.unit;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            switch (unit) {
							 | 
						||
| 
								 | 
							
								                                // is not supported until IE11
							 | 
						||
| 
								 | 
							
								                                case 'rem':
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                                // v* units is too buggy across browsers and better
							 | 
						||
| 
								 | 
							
								                                // don't merge values with those units
							 | 
						||
| 
								 | 
							
								                                case 'vw':
							 | 
						||
| 
								 | 
							
								                                case 'vh':
							 | 
						||
| 
								 | 
							
								                                case 'vmin':
							 | 
						||
| 
								 | 
							
								                                case 'vmax':
							 | 
						||
| 
								 | 
							
								                                case 'vm': // IE9 supporting "vm" instead of "vmin".
							 | 
						||
| 
								 | 
							
								                                    special[unit] = true;
							 | 
						||
| 
								 | 
							
								                                    break;
							 | 
						||
| 
								 | 
							
								                            }
							 | 
						||
| 
								 | 
							
								                            break;
							 | 
						||
| 
								 | 
							
								                    }
							 | 
						||
| 
								 | 
							
								                });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                fingerprint = raw
							 | 
						||
| 
								 | 
							
								                    ? '!' + fingerprintId++
							 | 
						||
| 
								 | 
							
								                    : '!' + Object.keys(special).sort() + '|' + iehack + vendorId;
							 | 
						||
| 
								 | 
							
								                break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            case 'Raw':
							 | 
						||
| 
								 | 
							
								                fingerprint = '!' + declaration.value.value;
							 | 
						||
| 
								 | 
							
								                break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            default:
							 | 
						||
| 
								 | 
							
								                fingerprint = generate(declaration.value);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        fingerprints[declarationId] = fingerprint;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return propertyName + fingerprint;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function needless(props, declaration, fingerprints) {
							 | 
						||
| 
								 | 
							
								    var property = resolveProperty(declaration.property);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (NEEDLESS_TABLE.hasOwnProperty(property.basename)) {
							 | 
						||
| 
								 | 
							
								        var table = NEEDLESS_TABLE[property.basename];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for (var i = 0; i < table.length; i++) {
							 | 
						||
| 
								 | 
							
								            var ppre = getPropertyFingerprint(property.prefix + table[i], declaration, fingerprints);
							 | 
						||
| 
								 | 
							
								            var prev = props.hasOwnProperty(ppre) ? props[ppre] : null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (prev && (!declaration.important || prev.item.data.important)) {
							 | 
						||
| 
								 | 
							
								                return prev;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function processRule(rule, item, list, props, fingerprints) {
							 | 
						||
| 
								 | 
							
								    var declarations = rule.block.children;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    declarations.eachRight(function(declaration, declarationItem) {
							 | 
						||
| 
								 | 
							
								        var property = declaration.property;
							 | 
						||
| 
								 | 
							
								        var fingerprint = getPropertyFingerprint(property, declaration, fingerprints);
							 | 
						||
| 
								 | 
							
								        var prev = props[fingerprint];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (prev && !dontRestructure.hasOwnProperty(property)) {
							 | 
						||
| 
								 | 
							
								            if (declaration.important && !prev.item.data.important) {
							 | 
						||
| 
								 | 
							
								                props[fingerprint] = {
							 | 
						||
| 
								 | 
							
								                    block: declarations,
							 | 
						||
| 
								 | 
							
								                    item: declarationItem
							 | 
						||
| 
								 | 
							
								                };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                prev.block.remove(prev.item);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                // TODO: use it when we can refer to several points in source
							 | 
						||
| 
								 | 
							
								                // declaration.loc = {
							 | 
						||
| 
								 | 
							
								                //     primary: declaration.loc,
							 | 
						||
| 
								 | 
							
								                //     merged: prev.item.data.loc
							 | 
						||
| 
								 | 
							
								                // };
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								                declarations.remove(declarationItem);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                // TODO: use it when we can refer to several points in source
							 | 
						||
| 
								 | 
							
								                // prev.item.data.loc = {
							 | 
						||
| 
								 | 
							
								                //     primary: prev.item.data.loc,
							 | 
						||
| 
								 | 
							
								                //     merged: declaration.loc
							 | 
						||
| 
								 | 
							
								                // };
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								            var prev = needless(props, declaration, fingerprints);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (prev) {
							 | 
						||
| 
								 | 
							
								                declarations.remove(declarationItem);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                // TODO: use it when we can refer to several points in source
							 | 
						||
| 
								 | 
							
								                // prev.item.data.loc = {
							 | 
						||
| 
								 | 
							
								                //     primary: prev.item.data.loc,
							 | 
						||
| 
								 | 
							
								                //     merged: declaration.loc
							 | 
						||
| 
								 | 
							
								                // };
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								                declaration.fingerprint = fingerprint;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                props[fingerprint] = {
							 | 
						||
| 
								 | 
							
								                    block: declarations,
							 | 
						||
| 
								 | 
							
								                    item: declarationItem
							 | 
						||
| 
								 | 
							
								                };
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (declarations.isEmpty()) {
							 | 
						||
| 
								 | 
							
								        list.remove(item);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = function restructBlock(ast) {
							 | 
						||
| 
								 | 
							
								    var stylesheetMap = {};
							 | 
						||
| 
								 | 
							
								    var fingerprints = Object.create(null);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    walk(ast, {
							 | 
						||
| 
								 | 
							
								        visit: 'Rule',
							 | 
						||
| 
								 | 
							
								        reverse: true,
							 | 
						||
| 
								 | 
							
								        enter: function(node, item, list) {
							 | 
						||
| 
								 | 
							
								            var stylesheet = this.block || this.stylesheet;
							 | 
						||
| 
								 | 
							
								            var ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first().id;
							 | 
						||
| 
								 | 
							
								            var ruleMap;
							 | 
						||
| 
								 | 
							
								            var props;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (!stylesheetMap.hasOwnProperty(stylesheet.id)) {
							 | 
						||
| 
								 | 
							
								                ruleMap = {};
							 | 
						||
| 
								 | 
							
								                stylesheetMap[stylesheet.id] = ruleMap;
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								                ruleMap = stylesheetMap[stylesheet.id];
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (ruleMap.hasOwnProperty(ruleId)) {
							 | 
						||
| 
								 | 
							
								                props = ruleMap[ruleId];
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								                props = {};
							 | 
						||
| 
								 | 
							
								                ruleMap[ruleId] = props;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            processRule.call(this, node, item, list, props, fingerprints);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								};
							 |