+
+var processableInfo = require('./processable');
+var overrideCompactor = require('./override-compactor');
+var shorthandCompactor = require('./shorthand-compactor');
+
module.exports = function Optimizer(compatibility) {
var overridable = {
'animation-delay': ['animation'],
return flat.join(';');
};
- var p = require('./processable');
- var overrideCompactor = require('./override-compactor');
- var shorthandCompactor = require('./shorthand-compactor');
-
var compact = function (input) {
- var tokens = p.Token.tokenize(input);
+ var processable = processableInfo.processable;
+ var Token = processableInfo.Token;
+
+ var tokens = Token.tokenize(input);
- tokens = overrideCompactor.compactOverrides(tokens, p.processable);
- tokens = shorthandCompactor.compactShorthands(tokens, false, p.processable, p.Token);
- tokens = shorthandCompactor.compactShorthands(tokens, true, p.processable, p.Token);
+ tokens = overrideCompactor.compactOverrides(tokens, processable);
+ tokens = shorthandCompactor.compactShorthands(tokens, false, processable, Token);
+ tokens = shorthandCompactor.compactShorthands(tokens, true, processable, Token);
- return p.Token.detokenize(tokens);
+ return Token.detokenize(tokens);
};
return {
// Compacts the given tokens according to their ability to override each other.
module.exports = (function () {
- var sameValue = function (val1, val2) { return val1 === val2; };
+ // Default override function: only allow overrides when the two values are the same
+ var sameValue = function (val1, val2) {
+ return val1 === val2;
+ };
var compactOverrides = function (tokens, processable) {
var result, can, token, t, i, ii, oldResult, matchingComponent;
};
// Go from the end and always take what the current token can't override as the new result set
+ // NOTE: can't cache result.length here because it will change with every iteration
for (result = tokens, i = 0; (ii = result.length - 1 - i) >= 0; i++) {
token = result[ii];
- //console.log(i, ii, token.prop);
can = (processable[token.prop] && processable[token.prop].canOverride) || sameValue;
oldResult = result;
result = [];
// Special flag which indicates that the current token should be removed
var removeSelf = false;
+ var oldResultLength = oldResult.length;
- for (var iii = 0; iii < oldResult.length; iii++) {
+ for (var iii = 0; iii < oldResultLength; iii++) {
t = oldResult[iii];
// A token can't override itself (checked by reference, not by value)
// The shorthand can't override the component
result.push(t);
}
- }
- else if (t.isShorthand && !token.isShorthand && token.isComponentOf(t)) {
+ } else if (t.isShorthand && !token.isShorthand && token.isComponentOf(t)) {
// token (a component) is trying to override a component of t (a shorthand)
// Find the matching component in the shorthand
matchingComponent.value = token.value;
// We use the special flag to get rid of the component
removeSelf = true;
- }
- else {
+ } else {
// The overriding component is important; sadly we can't get rid of it,
// but we can still mark the matching component in the shorthand as irrelevant
matchingComponent.isIrrelevant = true;
t.isDirty = true;
}
result.push(t);
- }
- else if (token.isShorthand && t.isShorthand && token.prop === t.prop) {
+ } else if (token.isShorthand && t.isShorthand && token.prop === t.prop) {
// token is a shorthand and is trying to override another instance of the same shorthand
// Can only override other shorthand when each of its components can override each of the other's components
break;
}
}
- }
- else if (t.prop !== token.prop || !can(t.value, token.value)) {
+ } else if (t.prop !== token.prop || !can(t.value, token.value)) {
// in every other case, use the override mechanism
result.push(t);
}
// Contains the interpretation of CSS properties, as used by the property optimizer
+
module.exports = (function () {
+ var tokenModule = require('./token');
var validator = require('./validator');
// Functions that decide what value can override what.
var result = [];
var curr = '';
var parenthesisLevel = 0;
+ var valLength = val.length;
- for (var i = 0; i < val.length; i++) {
+ for (var i = 0; i < valLength; i++) {
var c = val[i];
curr += c;
if (c === '(') {
parenthesisLevel++;
- }
- else if (c === ')') {
+ } else if (c === ')') {
parenthesisLevel--;
if (parenthesisLevel === 0) {
result.push(curr.trim());
curr = '';
}
- }
- else if (c === ' ' && parenthesisLevel === 0) {
+ } else if (c === ' ' && parenthesisLevel === 0) {
result.push(curr.trim());
curr = '';
}
// Break the background up into parts
var parts = token.value.split(' ');
- if (parts.length === 0) {
+ if (parts.length === 0)
return result;
- }
// The trick here is that we start going through the parts from the end, then stop after background repeat,
// then start from the from the beginning until we find a valid color value. What remains will be treated as background-image.
position.value = pos;
currentIndex -= 2;
current = parts[currentIndex];
- }
- else if (currentIndex >= 0 && validator.isValidBackgroundPosition(current)) {
+ } else if (currentIndex >= 0 && validator.isValidBackgroundPosition(current)) {
// Found position (containing just one part)
position.value = current;
currentIndex--;
fourUnits: function (prop, tokens, isImportant) {
// See about irrelevant tokens
// NOTE: This will enable some crazy optimalizations for us.
- if (tokens[0].isIrrelevant) {
+ if (tokens[0].isIrrelevant)
tokens[0].value = tokens[2].value;
- }
- if (tokens[2].isIrrelevant) {
+ if (tokens[2].isIrrelevant)
tokens[2].value = tokens[0].value;
- }
- if (tokens[1].isIrrelevant) {
+ if (tokens[1].isIrrelevant)
tokens[1].value = tokens[3].value;
- }
- if (tokens[3].isIrrelevant) {
+ if (tokens[3].isIrrelevant)
tokens[3].value = tokens[1].value;
- }
+
if (tokens[0].isIrrelevant && tokens[2].isIrrelevant) {
- if (tokens[1].value === tokens[3].value) {
+ if (tokens[1].value === tokens[3].value)
tokens[0].value = tokens[2].value = tokens[1].value;
- }
- else {
+ else
tokens[0].value = tokens[2].value = '0';
- }
}
if (tokens[1].isIrrelevant && tokens[3].isIrrelevant) {
- if (tokens[0].value === tokens[2].value) {
+ if (tokens[0].value === tokens[2].value)
tokens[1].value = tokens[3].value = tokens[0].value;
- }
- else {
+ else
tokens[1].value = tokens[3].value = '0';
- }
}
var result = new Token(prop, tokens[0].value, isImportant);
var r2s = new Token(tokens[i].prop, tokens[i].isImportant);
r2s.isIrrelevant = true;
result2Shorthandable.push(r2s);
- }
- else {
+ } else {
nonInheritingTokens.push(tokens[i]);
result2Shorthandable.push(tokens[i]);
}
}
- // When all the tokens are 'inherit'
if (nonInheritingTokens.length === 0) {
+ // When all the tokens are 'inherit'
return new Token(prop, 'inherit', isImportant);
- }
- // When some (but not all) of the tokens are 'inherit'
- else if (inheritingTokens.length > 0) {
+ } else if (inheritingTokens.length > 0) {
+ // When some (but not all) of the tokens are 'inherit'
+
// Result 1. Shorthand just the inherit values and have it overridden with the non-inheriting ones
var result1 = [new Token(prop, 'inherit', isImportant)].concat(nonInheritingTokens);
var dl2 = Token.getDetokenizedLength(result2);
return dl1 < dl2 ? result1 : result2;
- }
- // When none of tokens are 'inherit'
- else {
+ } else {
+ // When none of tokens are 'inherit'
return innerFunc(prop, tokens, isImportant);
}
};
// Set some stuff iteratively
for (var proc in processable) {
- if (processable.hasOwnProperty(proc)) {
- var currDesc = processable[proc];
+ if (!processable.hasOwnProperty(proc))
+ continue;
- if (currDesc.components instanceof Array && currDesc.components.length) {
- currDesc.isShorthand = true;
+ var currDesc = processable[proc];
- for (var cI = 0; cI < currDesc.components.length; cI++) {
- if (!processable[currDesc.components[cI]]) {
- throw new Error('"' + currDesc.components[cI] + '" is defined as a component of "' + proc + '" but isn\'t defined in processable.');
- }
- processable[currDesc.components[cI]].componentOf = proc;
- }
+ if (!(currDesc.components instanceof Array) || currDesc.components.length === 0)
+ continue;
+
+ currDesc.isShorthand = true;
+
+ for (var cI = 0; cI < currDesc.components.length; cI++) {
+ if (!processable[currDesc.components[cI]]) {
+ throw new Error('"' + currDesc.components[cI] + '" is defined as a component of "' + proc + '" but isn\'t defined in processable.');
}
+ processable[currDesc.components[cI]].componentOf = proc;
}
}
- var Token = require('./token').createTokenPrototype(processable);
+ var Token = tokenModule.createTokenPrototype(processable);
return {
processable: processable,
var prop = processable[shprop].components[i];
found[prop] = [];
- if (componentsSoFar[shprop].found[prop]) {
- for (var ii = 0; ii < componentsSoFar[shprop].found[prop].length; ii++) {
- var comp = componentsSoFar[shprop].found[prop][ii];
-
- if (!comp.isMarkedForDeletion) {
- found[prop].push(comp);
- if (comp.position && (!shorthandPosition || comp.position < shorthandPosition)) {
- shorthandPosition = comp.position;
- }
- }
- }
+ if (!(componentsSoFar[shprop].found[prop]))
+ continue;
+
+ for (var ii = 0; ii < componentsSoFar[shprop].found[prop].length; ii++) {
+ var comp = componentsSoFar[shprop].found[prop][ii];
+
+ if (comp.isMarkedForDeletion)
+ continue;
+
+ found[prop].push(comp);
+
+ if (comp.position && (!shorthandPosition || comp.position < shorthandPosition))
+ shorthandPosition = comp.position;
}
}
}
if (foundRealComp.isReal !== false) {
realComponents.push(foundRealComp);
}
- }
- else if (componentsSoFar[prop].lastShorthand) {
+ } else if (componentsSoFar[prop].lastShorthand) {
// It's defined in the previous shorthand
var c = componentsSoFar[prop].lastShorthand.components[i].clone(isImportant);
components.push(c);
- }
- else {
+ } else {
// Couldn't find this component at all
return false;
}
// TODO: when the old optimizer is removed, take care of this corner case:
// div{background-color:#111;background-image:url(aaa);background:linear-gradient(aaa);background-repeat:no-repeat;background-position:1px 2px;background-attachment:scroll}
// -> should not be shorthanded / minified at all because the result wouldn't be equivalent to the original in any browser
- }
- else if (processable[token.prop].componentOf) {
+ } else if (processable[token.prop].componentOf) {
// Found a component of a shorthand
if (token.isImportant === isImportant) {
// Same importantness
token.position = i;
addComponentSoFar(token, i);
- }
- else if (!isImportant && token.isImportant) {
+ } else if (!isImportant && token.isImportant) {
// Use importants for optimalization opportunities
// https://github.com/GoalSmashers/clean-css/issues/184
var importantTrickComp = new Token(token.prop, token.value, isImportant);
importantTrickComp.isReal = false;
addComponentSoFar(importantTrickComp);
}
- }
- else {
+ } else {
// This is not a shorthand and not a component, don't care about it
continue;
}
module.exports = (function () {
// Regexes used for stuff
- var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(px|%|em|rem|in|cm|mm|ex|pt|pc|)|auto|inherit)';
+ var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(px|%|em|rem|in|cm|mm|ex|pt|pc|)|auto|inherit)';
var cssFunctionNoVendorRegexStr = '[A-Z]+(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.)*\\)';
- var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.)*\\)';
- var cssFunctionAnyRegexStr = '(' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
- var cssUnitAnyRegexStr = '(' + cssUnitRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
+ var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.)*\\)';
+ var cssFunctionAnyRegexStr = '(' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
+ var cssUnitAnyRegexStr = '(' + cssUnitRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
+
+ var backgroundRepeatKeywords = ['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'inherit'];
+ var backgroundAttachmentKeywords = ['inherit', 'scroll', 'fixed', 'local'];
+ var backgroundPositionKeywords = ['center', 'top', 'bottom', 'left', 'right'];
+ var listStyleTypeKeywords = ['armenian', 'circle', 'cjk-ideographic', 'decimal', 'decimal-leading-zero', 'disc', 'georgian', 'hebrew', 'hiragana', 'hiragana-iroha', 'inherit', 'katakana', 'katakana-iroha', 'lower-alpha', 'lower-greek', 'lower-latin', 'lower-roman', 'none', 'square', 'upper-alpha', 'upper-latin', 'upper-roman'];
+ var listStylePositionKeywords = ['inside', 'outside', 'inherit'];
+ var outlineStyleKeywords = ['inherit', 'hidden', 'none', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'];
+ var outlineWidthKeywords = ['thin', 'thick', 'medium', 'inherit'];
var validator = {
isValidHexColor: function (s) {
return new RegExp('^' + cssFunctionAnyRegexStr + '$', 'gi').test(s);
},
isValidBackgroundRepeat: function (s) {
- return s === 'repeat' || s === 'no-repeat' || s === 'repeat-x' || s === 'repeat-y' || s === 'inherit';
+ return backgroundRepeatKeywords.indexOf(s) >= 0;
},
isValidBackgroundAttachment: function (s) {
- return s === 'inherit' || s === 'scroll' || s === 'fixed' || s === 'local';
+ return backgroundAttachmentKeywords.indexOf(s) >= 0;
},
isValidBackgroundPositionPart: function (s) {
- if (s === 'center' || s === 'top' || s === 'bottom' || s === 'left' || s === 'right')
+ if (backgroundPositionKeywords.indexOf(s) >= 0)
return true;
- // LIMITATION: currently we don't support functions in here because otherwise we'd confuse things like linear-gradient()
- // we need to figure out the complete list of functions that are allowed for units and then we can use isValidUnit here.
+
return new RegExp('^' + cssUnitRegexStr + '$', 'gi').test(s);
},
isValidBackgroundPosition: function (s) {
if (s === 'inherit')
return true;
- return s.split(' ').every(function(p) { return validator.isValidBackgroundPositionPart(p); });
+
+ return s.split(' ').filter(function (p) {
+ return p !== '';
+ }).every(function(p) {
+ return validator.isValidBackgroundPositionPart(p);
+ });
},
isValidListStyleType: function (s) {
- return s === 'armenian' || s === 'circle' || s === 'cjk-ideographic' || s === 'decimal' || s === 'decimal-leading-zero' || s === 'disc' || s === 'georgian' || s === 'hebrew' || s === 'hiragana' || s === 'hiragana-iroha' || s === 'inherit' || s === 'katakana' || s === 'katakana-iroha' || s === 'lower-alpha' || s === 'lower-greek' || s === 'lower-latin' || s === 'lower-roman' || s === 'none' || s === 'square' || s === 'upper-alpha' || s === 'upper-latin' || s === 'upper-roman';
+ return listStyleTypeKeywords.indexOf(s) >= 0;
},
isValidListStylePosition: function (s) {
- return s === 'inside' || s === 'outside' || s === 'inherit';
+ return listStylePositionKeywords.indexOf(s) >= 0;
},
isValidOutlineColor: function (s) {
return s === 'invert' || validator.isValidColor(s) || validator.isValidVendorPrefixedValue(s);
},
isValidOutlineStyle: function (s) {
- return s === 'inherit' || s === 'hidden' || s === 'none' || s === 'dotted' || s === 'dashed' || s === 'solid' || s === 'double' || s === 'groove' || s === 'ridge' || s === 'inset' || s === 'outset';
+ return outlineStyleKeywords.indexOf(s) >= 0;
},
isValidOutlineWidth: function (s) {
- return validator.isValidUnit(s) || s === 'thin' || s === 'thick' || s === 'medium' || s === 'inherit';
+ return validator.isValidUnit(s) || outlineWidthKeywords.indexOf(s) >= 0;
},
isValidVendorPrefixedValue: function (s) {
return /^-([A-Za-z0-9]|-)*$/gi.test(s);