No Description

shorthand-compactor.js 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // Compacts the tokens by transforming properties into their shorthand notations when possible
  2. module.exports = (function () {
  3. var isHackValue = function (t) { return t.value === '__hack'; };
  4. var compactShorthands = function(tokens, isImportant, processable, Token) {
  5. // Contains the components found so far, grouped by shorthand name
  6. var componentsSoFar = { };
  7. // Initializes a prop in componentsSoFar
  8. var initSoFar = function (shprop, last, clearAll) {
  9. var found = {};
  10. var shorthandPosition;
  11. if (!clearAll && componentsSoFar[shprop]) {
  12. for (var i = 0; i < processable[shprop].components.length; i++) {
  13. var prop = processable[shprop].components[i];
  14. found[prop] = [];
  15. if (!(componentsSoFar[shprop].found[prop]))
  16. continue;
  17. for (var ii = 0; ii < componentsSoFar[shprop].found[prop].length; ii++) {
  18. var comp = componentsSoFar[shprop].found[prop][ii];
  19. if (comp.isMarkedForDeletion)
  20. continue;
  21. found[prop].push(comp);
  22. if (comp.position && (!shorthandPosition || comp.position < shorthandPosition))
  23. shorthandPosition = comp.position;
  24. }
  25. }
  26. }
  27. componentsSoFar[shprop] = {
  28. lastShorthand: last,
  29. found: found,
  30. shorthandPosition: shorthandPosition
  31. };
  32. };
  33. // Adds a component to componentsSoFar
  34. var addComponentSoFar = function (token, index) {
  35. var shprop = processable[token.prop].componentOf;
  36. if (!componentsSoFar[shprop])
  37. initSoFar(shprop);
  38. if (!componentsSoFar[shprop].found[token.prop])
  39. componentsSoFar[shprop].found[token.prop] = [];
  40. // Add the newfound component to componentsSoFar
  41. componentsSoFar[shprop].found[token.prop].push(token);
  42. if (!componentsSoFar[shprop].shorthandPosition && index) {
  43. // If the haven't decided on where the shorthand should go, put it in the place of this component
  44. componentsSoFar[shprop].shorthandPosition = index;
  45. }
  46. };
  47. // Tries to compact a prop in componentsSoFar
  48. var compactSoFar = function (prop) {
  49. var i;
  50. var componentsCount = processable[prop].components.length;
  51. // Check basics
  52. if (!componentsSoFar[prop] || !componentsSoFar[prop].found)
  53. return false;
  54. // Find components for the shorthand
  55. var components = [];
  56. var realComponents = [];
  57. for (i = 0 ; i < componentsCount; i++) {
  58. // Get property name
  59. var pp = processable[prop].components[i];
  60. if (componentsSoFar[prop].found[pp] && componentsSoFar[prop].found[pp].length) {
  61. // We really found it
  62. var foundRealComp = componentsSoFar[prop].found[pp][0];
  63. components.push(foundRealComp);
  64. if (foundRealComp.isReal !== false) {
  65. realComponents.push(foundRealComp);
  66. }
  67. } else if (componentsSoFar[prop].lastShorthand) {
  68. // It's defined in the previous shorthand
  69. var c = componentsSoFar[prop].lastShorthand.components[i].clone(isImportant);
  70. components.push(c);
  71. } else {
  72. // Couldn't find this component at all
  73. return false;
  74. }
  75. }
  76. if (realComponents.length === 0) {
  77. // Couldn't find enough components, sorry
  78. return false;
  79. }
  80. if (realComponents.length === componentsCount) {
  81. // When all the components are from real values, only allow shorthanding if their understandability allows it
  82. // This is the case when every component can override their default values, or when all of them use the same function
  83. var canOverrideDefault = true;
  84. var functionNameMatches = true;
  85. var functionName;
  86. for (var ci = 0; ci < realComponents.length; ci++) {
  87. var rc = realComponents[ci];
  88. if (!processable[rc.prop].canOverride(processable[rc.prop].defaultValue, rc.value)) {
  89. canOverrideDefault = false;
  90. }
  91. var iop = rc.value.indexOf('(');
  92. if (iop >= 0) {
  93. var otherFunctionName = rc.value.substring(0, iop);
  94. if (functionName)
  95. functionNameMatches = functionNameMatches && otherFunctionName === functionName;
  96. else
  97. functionName = otherFunctionName;
  98. }
  99. }
  100. if (!canOverrideDefault || !functionNameMatches)
  101. return false;
  102. }
  103. // Compact the components into a shorthand
  104. var compacted = processable[prop].putTogether(prop, components, isImportant);
  105. if (!(compacted instanceof Array)) {
  106. compacted = [compacted];
  107. }
  108. var compactedLength = Token.getDetokenizedLength(compacted);
  109. var authenticLength = Token.getDetokenizedLength(realComponents);
  110. if (realComponents.length === componentsCount || compactedLength < authenticLength || components.some(isHackValue)) {
  111. compacted[0].isShorthand = true;
  112. compacted[0].components = processable[prop].breakUp(compacted[0]);
  113. // Mark the granular components for deletion
  114. for (i = 0; i < realComponents.length; i++) {
  115. realComponents[i].isMarkedForDeletion = true;
  116. }
  117. // Mark the position of the new shorthand
  118. tokens[componentsSoFar[prop].shorthandPosition].replaceWith = compacted;
  119. // Reinitialize the thing for further compacting
  120. initSoFar(prop, compacted[0]);
  121. for (i = 1; i < compacted.length; i++) {
  122. addComponentSoFar(compacted[i]);
  123. }
  124. // Yes, we can keep the new shorthand!
  125. return true;
  126. }
  127. return false;
  128. };
  129. // Tries to compact all properties currently in componentsSoFar
  130. var compactAllSoFar = function () {
  131. for (var i in componentsSoFar) {
  132. if (componentsSoFar.hasOwnProperty(i)) {
  133. while (compactSoFar(i)) { }
  134. }
  135. }
  136. };
  137. var i, token;
  138. // Go through each token and collect components for each shorthand as we go on
  139. for (i = 0; i < tokens.length; i++) {
  140. token = tokens[i];
  141. if (token.isMarkedForDeletion) {
  142. continue;
  143. }
  144. if (!processable[token.prop]) {
  145. // We don't know what it is, move on
  146. continue;
  147. }
  148. if (processable[token.prop].isShorthand) {
  149. // Found an instance of a full shorthand
  150. // NOTE: we should NOT mix together tokens that come before and after the shorthands
  151. if (token.isImportant === isImportant || (token.isImportant && !isImportant)) {
  152. // Try to compact what we've found so far
  153. while (compactSoFar(token.prop)) { }
  154. // Reset
  155. initSoFar(token.prop, token, true);
  156. }
  157. // TODO: when the old optimizer is removed, take care of this corner case:
  158. // div{background-color:#111;background-image:url(aaa);background:linear-gradient(aaa);background-repeat:no-repeat;background-position:1px 2px;background-attachment:scroll}
  159. // -> should not be shorthanded / minified at all because the result wouldn't be equivalent to the original in any browser
  160. } else if (processable[token.prop].componentOf) {
  161. // Found a component of a shorthand
  162. if (token.isImportant === isImportant) {
  163. // Same importantness
  164. token.position = i;
  165. addComponentSoFar(token, i);
  166. } else if (!isImportant && token.isImportant) {
  167. // Use importants for optimalization opportunities
  168. // https://github.com/GoalSmashers/clean-css/issues/184
  169. var importantTrickComp = new Token(token.prop, token.value, isImportant);
  170. importantTrickComp.isIrrelevant = true;
  171. importantTrickComp.isReal = false;
  172. addComponentSoFar(importantTrickComp);
  173. }
  174. } else {
  175. // This is not a shorthand and not a component, don't care about it
  176. continue;
  177. }
  178. }
  179. // Perform all possible compactions
  180. compactAllSoFar();
  181. // Process the results - throw away stuff marked for deletion, insert compacted things, etc.
  182. var result = [];
  183. for (i = 0; i < tokens.length; i++) {
  184. token = tokens[i];
  185. if (token.replaceWith) {
  186. for (var ii = 0; ii < token.replaceWith.length; ii++) {
  187. result.push(token.replaceWith[ii]);
  188. }
  189. }
  190. if (!token.isMarkedForDeletion) {
  191. result.push(token);
  192. }
  193. token.isMarkedForDeletion = false;
  194. token.replaceWith = null;
  195. }
  196. return result;
  197. };
  198. return {
  199. compactShorthands: compactShorthands
  200. };
  201. })();