var compactable = require('./compactable'); var wrapForOptimizing = require('./wrap-for-optimizing').all; var populateComponents = require('./populate-components'); var compactOverrides = require('./override-compactor'); var compactShorthands = require('./shorthand-compactor'); var removeUnused = require('./remove-unused'); var restoreFromOptimizing = require('./restore-from-optimizing'); var stringifyProperty = require('../stringifier/one-time').property; var shorthands = { 'animation-delay': ['animation'], 'animation-direction': ['animation'], 'animation-duration': ['animation'], 'animation-fill-mode': ['animation'], 'animation-iteration-count': ['animation'], 'animation-name': ['animation'], 'animation-play-state': ['animation'], 'animation-timing-function': ['animation'], '-moz-animation-delay': ['-moz-animation'], '-moz-animation-direction': ['-moz-animation'], '-moz-animation-duration': ['-moz-animation'], '-moz-animation-fill-mode': ['-moz-animation'], '-moz-animation-iteration-count': ['-moz-animation'], '-moz-animation-name': ['-moz-animation'], '-moz-animation-play-state': ['-moz-animation'], '-moz-animation-timing-function': ['-moz-animation'], '-o-animation-delay': ['-o-animation'], '-o-animation-direction': ['-o-animation'], '-o-animation-duration': ['-o-animation'], '-o-animation-fill-mode': ['-o-animation'], '-o-animation-iteration-count': ['-o-animation'], '-o-animation-name': ['-o-animation'], '-o-animation-play-state': ['-o-animation'], '-o-animation-timing-function': ['-o-animation'], '-webkit-animation-delay': ['-webkit-animation'], '-webkit-animation-direction': ['-webkit-animation'], '-webkit-animation-duration': ['-webkit-animation'], '-webkit-animation-fill-mode': ['-webkit-animation'], '-webkit-animation-iteration-count': ['-webkit-animation'], '-webkit-animation-name': ['-webkit-animation'], '-webkit-animation-play-state': ['-webkit-animation'], '-webkit-animation-timing-function': ['-webkit-animation'], 'border-color': ['border'], 'border-style': ['border'], 'border-width': ['border'], 'border-bottom': ['border'], 'border-bottom-color': ['border-bottom', 'border-color', 'border'], 'border-bottom-style': ['border-bottom', 'border-style', 'border'], 'border-bottom-width': ['border-bottom', 'border-width', 'border'], 'border-left': ['border'], 'border-left-color': ['border-left', 'border-color', 'border'], 'border-left-style': ['border-left', 'border-style', 'border'], 'border-left-width': ['border-left', 'border-width', 'border'], 'border-right': ['border'], 'border-right-color': ['border-right', 'border-color', 'border'], 'border-right-style': ['border-right', 'border-style', 'border'], 'border-right-width': ['border-right', 'border-width', 'border'], 'border-top': ['border'], 'border-top-color': ['border-top', 'border-color', 'border'], 'border-top-style': ['border-top', 'border-style', 'border'], 'border-top-width': ['border-top', 'border-width', 'border'], 'font-family': ['font'], 'font-size': ['font'], 'font-style': ['font'], 'font-variant': ['font'], 'font-weight': ['font'], 'transition-delay': ['transition'], 'transition-duration': ['transition'], 'transition-property': ['transition'], 'transition-timing-function': ['transition'], '-moz-transition-delay': ['-moz-transition'], '-moz-transition-duration': ['-moz-transition'], '-moz-transition-property': ['-moz-transition'], '-moz-transition-timing-function': ['-moz-transition'], '-o-transition-delay': ['-o-transition'], '-o-transition-duration': ['-o-transition'], '-o-transition-property': ['-o-transition'], '-o-transition-timing-function': ['-o-transition'], '-webkit-transition-delay': ['-webkit-transition'], '-webkit-transition-duration': ['-webkit-transition'], '-webkit-transition-property': ['-webkit-transition'], '-webkit-transition-timing-function': ['-webkit-transition'] }; function _optimize(properties, mergeAdjacent, aggressiveMerging, validator) { var overrideMapping = {}; var lastName = null; var lastProperty; var j; function mergeablePosition(position) { if (mergeAdjacent === false || mergeAdjacent === true) return mergeAdjacent; return mergeAdjacent.indexOf(position) > -1; } function sameValue(position) { var left = properties[position - 1]; var right = properties[position]; return stringifyProperty(left.all, left.position) == stringifyProperty(right.all, right.position); } propertyLoop: for (var position = 0, total = properties.length; position < total; position++) { var property = properties[position]; var _name = (property.name == '-ms-filter' || property.name == 'filter') ? (lastName == 'background' || lastName == 'background-image' ? lastName : property.name) : property.name; var isImportant = property.important; var isHack = property.hack; if (property.unused) continue; if (position > 0 && lastProperty && _name == lastName && isImportant == lastProperty.important && isHack == lastProperty.hack && sameValue(position) && !lastProperty.unused) { property.unused = true; 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 // e.g. a{display:inline-block;display:-moz-inline-box} // however if `mergeablePosition` yields true then the rule does not apply // (e.g merging two adjacent selectors: `a{display:block}a{display:block}`) if (_name in overrideMapping && (aggressiveMerging && _name != lastName || mergeablePosition(position))) { var toOverridePositions = overrideMapping[_name]; var canOverride = compactable[_name] && compactable[_name].canOverride; var anyRemoved = false; for (j = toOverridePositions.length - 1; j >= 0; j--) { var toRemove = properties[toOverridePositions[j]]; var longhandToShorthand = toRemove.name != _name; var wasImportant = toRemove.important; var wasHack = toRemove.hack; if (toRemove.unused) continue; if (longhandToShorthand && wasImportant) continue; if (!wasImportant && (wasHack && !isHack || !wasHack && isHack)) continue; if (wasImportant && (isHack == 'star' || isHack == 'underscore')) continue; if (!wasHack && !isHack && !longhandToShorthand && canOverride && !canOverride(toRemove, property, validator)) continue; if (wasImportant && !isImportant || wasImportant && isHack) { property.unused = true; lastProperty = property; continue propertyLoop; } else { anyRemoved = true; toRemove.unused = true; } } if (anyRemoved) { position = -1; lastProperty = null; lastName = null; overrideMapping = {}; continue; } } else { overrideMapping[_name] = overrideMapping[_name] || []; overrideMapping[_name].push(position); // TODO: to be removed with // certain shorthand (see values of `shorthands`) should trigger removal of // longhand properties (see keys of `shorthands`) var _shorthands = shorthands[_name]; if (_shorthands) { for (j = _shorthands.length - 1; j >= 0; j--) { var shorthand = _shorthands[j]; overrideMapping[shorthand] = overrideMapping[shorthand] || []; overrideMapping[shorthand].push(position); } } } lastName = _name; lastProperty = property; } } function optimize(selector, properties, mergeAdjacent, withCompacting, options, context) { var validator = context.validator; var warnings = context.warnings; var _properties = wrapForOptimizing(properties); populateComponents(_properties, validator, warnings); _optimize(_properties, mergeAdjacent, options.aggressiveMerging, validator); for (var i = 0, l = _properties.length; i < l; i++) { var _property = _properties[i]; if (_property.variable && _property.block) optimize(selector, _property.value[0], mergeAdjacent, withCompacting, options, context); } if (withCompacting && options.shorthandCompacting) { compactOverrides(_properties, options.compatibility, validator); compactShorthands(_properties, options.sourceMap, validator); } restoreFromOptimizing(_properties); removeUnused(_properties); } module.exports = optimize;