optimizer.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. var compactable = require('./compactable');
  2. var wrapForOptimizing = require('./wrap-for-optimizing').all;
  3. var populateComponents = require('./populate-components');
  4. var compactOverrides = require('./override-compactor');
  5. var compactShorthands = require('./shorthand-compactor');
  6. var removeUnused = require('./remove-unused');
  7. var restoreFromOptimizing = require('./restore-from-optimizing');
  8. var stringifyProperty = require('../stringifier/one-time').property;
  9. var shorthands = {
  10. 'animation-delay': ['animation'],
  11. 'animation-direction': ['animation'],
  12. 'animation-duration': ['animation'],
  13. 'animation-fill-mode': ['animation'],
  14. 'animation-iteration-count': ['animation'],
  15. 'animation-name': ['animation'],
  16. 'animation-play-state': ['animation'],
  17. 'animation-timing-function': ['animation'],
  18. '-moz-animation-delay': ['-moz-animation'],
  19. '-moz-animation-direction': ['-moz-animation'],
  20. '-moz-animation-duration': ['-moz-animation'],
  21. '-moz-animation-fill-mode': ['-moz-animation'],
  22. '-moz-animation-iteration-count': ['-moz-animation'],
  23. '-moz-animation-name': ['-moz-animation'],
  24. '-moz-animation-play-state': ['-moz-animation'],
  25. '-moz-animation-timing-function': ['-moz-animation'],
  26. '-o-animation-delay': ['-o-animation'],
  27. '-o-animation-direction': ['-o-animation'],
  28. '-o-animation-duration': ['-o-animation'],
  29. '-o-animation-fill-mode': ['-o-animation'],
  30. '-o-animation-iteration-count': ['-o-animation'],
  31. '-o-animation-name': ['-o-animation'],
  32. '-o-animation-play-state': ['-o-animation'],
  33. '-o-animation-timing-function': ['-o-animation'],
  34. '-webkit-animation-delay': ['-webkit-animation'],
  35. '-webkit-animation-direction': ['-webkit-animation'],
  36. '-webkit-animation-duration': ['-webkit-animation'],
  37. '-webkit-animation-fill-mode': ['-webkit-animation'],
  38. '-webkit-animation-iteration-count': ['-webkit-animation'],
  39. '-webkit-animation-name': ['-webkit-animation'],
  40. '-webkit-animation-play-state': ['-webkit-animation'],
  41. '-webkit-animation-timing-function': ['-webkit-animation'],
  42. 'border-color': ['border'],
  43. 'border-style': ['border'],
  44. 'border-width': ['border'],
  45. 'border-bottom': ['border'],
  46. 'border-bottom-color': ['border-bottom', 'border-color', 'border'],
  47. 'border-bottom-style': ['border-bottom', 'border-style', 'border'],
  48. 'border-bottom-width': ['border-bottom', 'border-width', 'border'],
  49. 'border-left': ['border'],
  50. 'border-left-color': ['border-left', 'border-color', 'border'],
  51. 'border-left-style': ['border-left', 'border-style', 'border'],
  52. 'border-left-width': ['border-left', 'border-width', 'border'],
  53. 'border-right': ['border'],
  54. 'border-right-color': ['border-right', 'border-color', 'border'],
  55. 'border-right-style': ['border-right', 'border-style', 'border'],
  56. 'border-right-width': ['border-right', 'border-width', 'border'],
  57. 'border-top': ['border'],
  58. 'border-top-color': ['border-top', 'border-color', 'border'],
  59. 'border-top-style': ['border-top', 'border-style', 'border'],
  60. 'border-top-width': ['border-top', 'border-width', 'border'],
  61. 'font-family': ['font'],
  62. 'font-size': ['font'],
  63. 'font-style': ['font'],
  64. 'font-variant': ['font'],
  65. 'font-weight': ['font'],
  66. 'transition-delay': ['transition'],
  67. 'transition-duration': ['transition'],
  68. 'transition-property': ['transition'],
  69. 'transition-timing-function': ['transition'],
  70. '-moz-transition-delay': ['-moz-transition'],
  71. '-moz-transition-duration': ['-moz-transition'],
  72. '-moz-transition-property': ['-moz-transition'],
  73. '-moz-transition-timing-function': ['-moz-transition'],
  74. '-o-transition-delay': ['-o-transition'],
  75. '-o-transition-duration': ['-o-transition'],
  76. '-o-transition-property': ['-o-transition'],
  77. '-o-transition-timing-function': ['-o-transition'],
  78. '-webkit-transition-delay': ['-webkit-transition'],
  79. '-webkit-transition-duration': ['-webkit-transition'],
  80. '-webkit-transition-property': ['-webkit-transition'],
  81. '-webkit-transition-timing-function': ['-webkit-transition']
  82. };
  83. function _optimize(properties, mergeAdjacent, aggressiveMerging, validator) {
  84. var overrideMapping = {};
  85. var lastName = null;
  86. var lastProperty;
  87. var j;
  88. function mergeablePosition(position) {
  89. if (mergeAdjacent === false || mergeAdjacent === true)
  90. return mergeAdjacent;
  91. return mergeAdjacent.indexOf(position) > -1;
  92. }
  93. function sameValue(position) {
  94. var left = properties[position - 1];
  95. var right = properties[position];
  96. return stringifyProperty(left.all, left.position) == stringifyProperty(right.all, right.position);
  97. }
  98. propertyLoop:
  99. for (var position = 0, total = properties.length; position < total; position++) {
  100. var property = properties[position];
  101. var _name = (property.name == '-ms-filter' || property.name == 'filter') ?
  102. (lastName == 'background' || lastName == 'background-image' ? lastName : property.name) :
  103. property.name;
  104. var isImportant = property.important;
  105. var isHack = property.hack;
  106. if (property.unused)
  107. continue;
  108. if (position > 0 && lastProperty && _name == lastName && isImportant == lastProperty.important && isHack == lastProperty.hack && sameValue(position) && !lastProperty.unused) {
  109. property.unused = true;
  110. continue;
  111. }
  112. // comment is necessary - we assume that if two properties are one after another
  113. // then it is intentional way of redefining property which may not be widely supported
  114. // e.g. a{display:inline-block;display:-moz-inline-box}
  115. // however if `mergeablePosition` yields true then the rule does not apply
  116. // (e.g merging two adjacent selectors: `a{display:block}a{display:block}`)
  117. if (_name in overrideMapping && (aggressiveMerging && _name != lastName || mergeablePosition(position))) {
  118. var toOverridePositions = overrideMapping[_name];
  119. var canOverride = compactable[_name] && compactable[_name].canOverride;
  120. var anyRemoved = false;
  121. for (j = toOverridePositions.length - 1; j >= 0; j--) {
  122. var toRemove = properties[toOverridePositions[j]];
  123. var longhandToShorthand = toRemove.name != _name;
  124. var wasImportant = toRemove.important;
  125. var wasHack = toRemove.hack;
  126. if (toRemove.unused)
  127. continue;
  128. if (longhandToShorthand && wasImportant)
  129. continue;
  130. if (!wasImportant && (wasHack && !isHack || !wasHack && isHack))
  131. continue;
  132. if (wasImportant && (isHack == 'star' || isHack == 'underscore'))
  133. continue;
  134. if (!wasHack && !isHack && !longhandToShorthand && canOverride && !canOverride(toRemove, property, validator))
  135. continue;
  136. if (wasImportant && !isImportant || wasImportant && isHack) {
  137. property.unused = true;
  138. lastProperty = property;
  139. continue propertyLoop;
  140. } else {
  141. anyRemoved = true;
  142. toRemove.unused = true;
  143. }
  144. }
  145. if (anyRemoved) {
  146. position = -1;
  147. lastProperty = null;
  148. lastName = null;
  149. overrideMapping = {};
  150. continue;
  151. }
  152. } else {
  153. overrideMapping[_name] = overrideMapping[_name] || [];
  154. overrideMapping[_name].push(position);
  155. // TODO: to be removed with
  156. // certain shorthand (see values of `shorthands`) should trigger removal of
  157. // longhand properties (see keys of `shorthands`)
  158. var _shorthands = shorthands[_name];
  159. if (_shorthands) {
  160. for (j = _shorthands.length - 1; j >= 0; j--) {
  161. var shorthand = _shorthands[j];
  162. overrideMapping[shorthand] = overrideMapping[shorthand] || [];
  163. overrideMapping[shorthand].push(position);
  164. }
  165. }
  166. }
  167. lastName = _name;
  168. lastProperty = property;
  169. }
  170. }
  171. function optimize(selector, properties, mergeAdjacent, withCompacting, options, context) {
  172. var validator = context.validator;
  173. var warnings = context.warnings;
  174. var _properties = wrapForOptimizing(properties);
  175. populateComponents(_properties, validator, warnings);
  176. _optimize(_properties, mergeAdjacent, options.aggressiveMerging, validator);
  177. for (var i = 0, l = _properties.length; i < l; i++) {
  178. var _property = _properties[i];
  179. if (_property.variable && _property.block)
  180. optimize(selector, _property.value[0], mergeAdjacent, withCompacting, options, context);
  181. }
  182. if (withCompacting && options.shorthandCompacting) {
  183. compactOverrides(_properties, options.compatibility, validator);
  184. compactShorthands(_properties, options.sourceMap, validator);
  185. }
  186. restoreFromOptimizing(_properties);
  187. removeUnused(_properties);
  188. }
  189. module.exports = optimize;