123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
-
- // 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
- };
-
- })();
|