backoffice/node_modules/clean-css/lib/properties/shorthand-compactor.js

245 lines
8.4 KiB
JavaScript

// Compacts the tokens by transforming properties into their shorthand notations when possible
module.exports = (function () {
var isHackValue = function (t) { return t.value === '__hack'; };
var compactShorthands = function(tokens, isImportant, processable, Token) {
// Contains the components found so far, grouped by shorthand name
var componentsSoFar = { };
// Initializes a prop in componentsSoFar
var initSoFar = function (shprop, last, clearAll) {
var found = {};
var shorthandPosition;
if (!clearAll && componentsSoFar[shprop]) {
for (var i = 0; i < processable[shprop].components.length; i++) {
var prop = processable[shprop].components[i];
found[prop] = [];
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;
}
}
}
componentsSoFar[shprop] = {
lastShorthand: last,
found: found,
shorthandPosition: shorthandPosition
};
};
// Adds a component to componentsSoFar
var addComponentSoFar = function (token, index) {
var shprop = processable[token.prop].componentOf;
if (!componentsSoFar[shprop])
initSoFar(shprop);
if (!componentsSoFar[shprop].found[token.prop])
componentsSoFar[shprop].found[token.prop] = [];
// Add the newfound component to componentsSoFar
componentsSoFar[shprop].found[token.prop].push(token);
if (!componentsSoFar[shprop].shorthandPosition && index) {
// If the haven't decided on where the shorthand should go, put it in the place of this component
componentsSoFar[shprop].shorthandPosition = index;
}
};
// Tries to compact a prop in componentsSoFar
var compactSoFar = function (prop) {
var i;
var componentsCount = processable[prop].components.length;
// Check basics
if (!componentsSoFar[prop] || !componentsSoFar[prop].found)
return false;
// Find components for the shorthand
var components = [];
var realComponents = [];
for (i = 0 ; i < componentsCount; i++) {
// Get property name
var pp = processable[prop].components[i];
if (componentsSoFar[prop].found[pp] && componentsSoFar[prop].found[pp].length) {
// We really found it
var foundRealComp = componentsSoFar[prop].found[pp][0];
components.push(foundRealComp);
if (foundRealComp.isReal !== false) {
realComponents.push(foundRealComp);
}
} 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 {
// Couldn't find this component at all
return false;
}
}
if (realComponents.length === 0) {
// Couldn't find enough components, sorry
return false;
}
if (realComponents.length === componentsCount) {
// When all the components are from real values, only allow shorthanding if their understandability allows it
// This is the case when every component can override their default values, or when all of them use the same function
var canOverrideDefault = true;
var functionNameMatches = true;
var functionName;
for (var ci = 0; ci < realComponents.length; ci++) {
var rc = realComponents[ci];
if (!processable[rc.prop].canOverride(processable[rc.prop].defaultValue, rc.value)) {
canOverrideDefault = false;
}
var iop = rc.value.indexOf('(');
if (iop >= 0) {
var otherFunctionName = rc.value.substring(0, iop);
if (functionName)
functionNameMatches = functionNameMatches && otherFunctionName === functionName;
else
functionName = otherFunctionName;
}
}
if (!canOverrideDefault || !functionNameMatches)
return false;
}
// Compact the components into a shorthand
var compacted = processable[prop].putTogether(prop, components, isImportant);
if (!(compacted instanceof Array)) {
compacted = [compacted];
}
var compactedLength = Token.getDetokenizedLength(compacted);
var authenticLength = Token.getDetokenizedLength(realComponents);
if (realComponents.length === componentsCount || compactedLength < authenticLength || components.some(isHackValue)) {
compacted[0].isShorthand = true;
compacted[0].components = processable[prop].breakUp(compacted[0]);
// Mark the granular components for deletion
for (i = 0; i < realComponents.length; i++) {
realComponents[i].isMarkedForDeletion = true;
}
// Mark the position of the new shorthand
tokens[componentsSoFar[prop].shorthandPosition].replaceWith = compacted;
// Reinitialize the thing for further compacting
initSoFar(prop, compacted[0]);
for (i = 1; i < compacted.length; i++) {
addComponentSoFar(compacted[i]);
}
// Yes, we can keep the new shorthand!
return true;
}
return false;
};
// Tries to compact all properties currently in componentsSoFar
var compactAllSoFar = function () {
for (var i in componentsSoFar) {
if (componentsSoFar.hasOwnProperty(i)) {
while (compactSoFar(i)) { }
}
}
};
var i, token;
// Go through each token and collect components for each shorthand as we go on
for (i = 0; i < tokens.length; i++) {
token = tokens[i];
if (token.isMarkedForDeletion) {
continue;
}
if (!processable[token.prop]) {
// We don't know what it is, move on
continue;
}
if (processable[token.prop].isShorthand) {
// Found an instance of a full shorthand
// NOTE: we should NOT mix together tokens that come before and after the shorthands
if (token.isImportant === isImportant || (token.isImportant && !isImportant)) {
// Try to compact what we've found so far
while (compactSoFar(token.prop)) { }
// Reset
initSoFar(token.prop, token, true);
}
// 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) {
// Found a component of a shorthand
if (token.isImportant === isImportant) {
// Same importantness
token.position = i;
addComponentSoFar(token, i);
} 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.isIrrelevant = true;
importantTrickComp.isReal = false;
addComponentSoFar(importantTrickComp);
}
} else {
// This is not a shorthand and not a component, don't care about it
continue;
}
}
// Perform all possible compactions
compactAllSoFar();
// Process the results - throw away stuff marked for deletion, insert compacted things, etc.
var result = [];
for (i = 0; i < tokens.length; i++) {
token = tokens[i];
if (token.replaceWith) {
for (var ii = 0; ii < token.replaceWith.length; ii++) {
result.push(token.replaceWith[ii]);
}
}
if (!token.isMarkedForDeletion) {
result.push(token);
}
token.isMarkedForDeletion = false;
token.replaceWith = null;
}
return result;
};
return {
compactShorthands: compactShorthands
};
})();