Нема описа

optimizer.js 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. var processableInfo = require('./processable');
  2. var overrideCompactor = require('./override-compactor');
  3. var shorthandCompactor = require('./shorthand-compactor');
  4. module.exports = function Optimizer(compatibility, aggressiveMerging, context) {
  5. var overridable = {
  6. 'animation-delay': ['animation'],
  7. 'animation-direction': ['animation'],
  8. 'animation-duration': ['animation'],
  9. 'animation-fill-mode': ['animation'],
  10. 'animation-iteration-count': ['animation'],
  11. 'animation-name': ['animation'],
  12. 'animation-play-state': ['animation'],
  13. 'animation-timing-function': ['animation'],
  14. '-moz-animation-delay': ['-moz-animation'],
  15. '-moz-animation-direction': ['-moz-animation'],
  16. '-moz-animation-duration': ['-moz-animation'],
  17. '-moz-animation-fill-mode': ['-moz-animation'],
  18. '-moz-animation-iteration-count': ['-moz-animation'],
  19. '-moz-animation-name': ['-moz-animation'],
  20. '-moz-animation-play-state': ['-moz-animation'],
  21. '-moz-animation-timing-function': ['-moz-animation'],
  22. '-o-animation-delay': ['-o-animation'],
  23. '-o-animation-direction': ['-o-animation'],
  24. '-o-animation-duration': ['-o-animation'],
  25. '-o-animation-fill-mode': ['-o-animation'],
  26. '-o-animation-iteration-count': ['-o-animation'],
  27. '-o-animation-name': ['-o-animation'],
  28. '-o-animation-play-state': ['-o-animation'],
  29. '-o-animation-timing-function': ['-o-animation'],
  30. '-webkit-animation-delay': ['-webkit-animation'],
  31. '-webkit-animation-direction': ['-webkit-animation'],
  32. '-webkit-animation-duration': ['-webkit-animation'],
  33. '-webkit-animation-fill-mode': ['-webkit-animation'],
  34. '-webkit-animation-iteration-count': ['-webkit-animation'],
  35. '-webkit-animation-name': ['-webkit-animation'],
  36. '-webkit-animation-play-state': ['-webkit-animation'],
  37. '-webkit-animation-timing-function': ['-webkit-animation'],
  38. 'background-clip': ['background'],
  39. 'background-origin': ['background'],
  40. 'border-color': ['border'],
  41. 'border-style': ['border'],
  42. 'border-width': ['border'],
  43. 'border-bottom': ['border'],
  44. 'border-bottom-color': ['border-bottom', 'border-color', 'border'],
  45. 'border-bottom-style': ['border-bottom', 'border-style', 'border'],
  46. 'border-bottom-width': ['border-bottom', 'border-width', 'border'],
  47. 'border-left': ['border'],
  48. 'border-left-color': ['border-left', 'border-color', 'border'],
  49. 'border-left-style': ['border-left', 'border-style', 'border'],
  50. 'border-left-width': ['border-left', 'border-width', 'border'],
  51. 'border-right': ['border'],
  52. 'border-right-color': ['border-right', 'border-color', 'border'],
  53. 'border-right-style': ['border-right', 'border-style', 'border'],
  54. 'border-right-width': ['border-right', 'border-width', 'border'],
  55. 'border-top': ['border'],
  56. 'border-top-color': ['border-top', 'border-color', 'border'],
  57. 'border-top-style': ['border-top', 'border-style', 'border'],
  58. 'border-top-width': ['border-top', 'border-width', 'border'],
  59. 'font-family': ['font'],
  60. 'font-size': ['font'],
  61. 'font-style': ['font'],
  62. 'font-variant': ['font'],
  63. 'font-weight': ['font'],
  64. 'list-style-image': ['list-style'],
  65. 'list-style-position': ['list-style'],
  66. 'list-style-type': ['list-style'],
  67. 'margin-bottom': ['margin'],
  68. 'margin-left': ['margin'],
  69. 'margin-right': ['margin'],
  70. 'margin-top': ['margin'],
  71. 'outline-color': ['outline'],
  72. 'outline-style': ['outline'],
  73. 'outline-width': ['outline'],
  74. 'padding-bottom': ['padding'],
  75. 'padding-left': ['padding'],
  76. 'padding-right': ['padding'],
  77. 'padding-top': ['padding'],
  78. 'transition-delay': ['transition'],
  79. 'transition-duration': ['transition'],
  80. 'transition-property': ['transition'],
  81. 'transition-timing-function': ['transition'],
  82. '-moz-transition-delay': ['-moz-transition'],
  83. '-moz-transition-duration': ['-moz-transition'],
  84. '-moz-transition-property': ['-moz-transition'],
  85. '-moz-transition-timing-function': ['-moz-transition'],
  86. '-o-transition-delay': ['-o-transition'],
  87. '-o-transition-duration': ['-o-transition'],
  88. '-o-transition-property': ['-o-transition'],
  89. '-o-transition-timing-function': ['-o-transition'],
  90. '-webkit-transition-delay': ['-webkit-transition'],
  91. '-webkit-transition-duration': ['-webkit-transition'],
  92. '-webkit-transition-property': ['-webkit-transition'],
  93. '-webkit-transition-timing-function': ['-webkit-transition']
  94. };
  95. var IE_BACKSLASH_HACK = '\\9';
  96. var overrides = {};
  97. for (var granular in overridable) {
  98. for (var i = 0; i < overridable[granular].length; i++) {
  99. var coarse = overridable[granular][i];
  100. var list = overrides[coarse];
  101. if (list)
  102. list.push(granular);
  103. else
  104. overrides[coarse] = [granular];
  105. }
  106. }
  107. var tokenize = function(body, selector) {
  108. var tokens = body.split(';');
  109. var keyValues = [];
  110. if (tokens.length === 0 || (tokens.length == 1 && tokens[0].indexOf(IE_BACKSLASH_HACK) == -1 && tokens[0][tokens[0].length - 1] != ':'))
  111. return;
  112. for (var i = 0, l = tokens.length; i < l; i++) {
  113. var token = tokens[i];
  114. if (token === '')
  115. continue;
  116. var firstColon = token.indexOf(':');
  117. var property = token.substring(0, firstColon);
  118. var value = token.substring(firstColon + 1);
  119. if (value === '') {
  120. context.warnings.push('Empty property \'' + property + '\' inside \'' + selector + '\' selector. Ignoring.');
  121. continue;
  122. }
  123. keyValues.push([
  124. property,
  125. value,
  126. token.indexOf('!important') > -1,
  127. token.indexOf(IE_BACKSLASH_HACK, firstColon + 1) === token.length - IE_BACKSLASH_HACK.length
  128. ]);
  129. }
  130. return keyValues;
  131. };
  132. var optimize = function(tokens, allowAdjacent) {
  133. var merged = [];
  134. var properties = [];
  135. var lastProperty = null;
  136. var rescanTrigger = {};
  137. var removeOverridenBy = function(property, isImportant) {
  138. var overrided = overrides[property];
  139. for (var i = 0, l = overrided.length; i < l; i++) {
  140. for (var j = 0; j < properties.length; j++) {
  141. if (properties[j] != overrided[i] || (merged[j][2] && !isImportant))
  142. continue;
  143. merged.splice(j, 1);
  144. properties.splice(j, 1);
  145. j -= 1;
  146. }
  147. }
  148. };
  149. var mergeablePosition = function(position) {
  150. if (allowAdjacent === false || allowAdjacent === true)
  151. return allowAdjacent;
  152. return allowAdjacent.indexOf(position) > -1;
  153. };
  154. tokensLoop:
  155. for (var i = 0, l = tokens.length; i < l; i++) {
  156. var token = tokens[i];
  157. var property = token[0];
  158. var value = token[1];
  159. var isImportant = token[2];
  160. var isIEHack = token[3];
  161. var _property = (property == '-ms-filter' || property == 'filter') ?
  162. (lastProperty == 'background' || lastProperty == 'background-image' ? lastProperty : property) :
  163. property;
  164. var toOverridePosition = 0;
  165. if (!compatibility && isIEHack)
  166. continue;
  167. // comment is necessary - we assume that if two properties are one after another
  168. // then it is intentional way of redefining property which may not be widely supported
  169. // e.g. a{display:inline-block;display:-moz-inline-box}
  170. // however if `mergeablePosition` yields true then the rule does not apply
  171. // (e.g merging two adjacent selectors: `a{display:block}a{display:block}`)
  172. if (aggressiveMerging && _property != lastProperty || mergeablePosition(i)) {
  173. while (true) {
  174. toOverridePosition = properties.indexOf(_property, toOverridePosition);
  175. if (toOverridePosition == -1)
  176. break;
  177. var lastToken = merged[toOverridePosition];
  178. var wasImportant = lastToken[2];
  179. var wasIEHack = lastToken[3];
  180. if (wasImportant && !isImportant)
  181. continue tokensLoop;
  182. if (compatibility && !wasIEHack && isIEHack)
  183. break;
  184. var _info = processableInfo.processable[_property];
  185. if (!isIEHack && !wasIEHack && _info && _info.canOverride && !_info.canOverride(tokens[toOverridePosition][1], value))
  186. break;
  187. merged.splice(toOverridePosition, 1);
  188. properties.splice(toOverridePosition, 1);
  189. }
  190. }
  191. merged.push(token);
  192. properties.push(_property);
  193. // certain properties (see values of `overridable`) should trigger removal of
  194. // more granular properties (see keys of `overridable`)
  195. if (rescanTrigger[_property])
  196. removeOverridenBy(_property, isImportant);
  197. // add rescan triggers - if certain property appears later in the list a rescan needs
  198. // to be triggered, e.g 'border-top' triggers a rescan after 'border-top-width' and
  199. // 'border-top-color' as they can be removed
  200. for (var j = 0, list = overridable[_property] || [], m = list.length; j < m; j++)
  201. rescanTrigger[list[j]] = true;
  202. lastProperty = _property;
  203. }
  204. return merged;
  205. };
  206. var rebuild = function(tokens) {
  207. var flat = [];
  208. for (var i = 0, l = tokens.length; i < l; i++) {
  209. flat.push(tokens[i][0] + ':' + tokens[i][1]);
  210. }
  211. return flat.join(';');
  212. };
  213. var compact = function (input) {
  214. var processable = processableInfo.processable;
  215. var Token = processableInfo.Token;
  216. var tokens = Token.tokenize(input);
  217. tokens = overrideCompactor.compactOverrides(tokens, processable, Token, compatibility);
  218. tokens = shorthandCompactor.compactShorthands(tokens, false, processable, Token);
  219. tokens = shorthandCompactor.compactShorthands(tokens, true, processable, Token);
  220. return Token.detokenize(tokens);
  221. };
  222. return {
  223. process: function(body, allowAdjacent, skipCompacting, selector) {
  224. var result = body;
  225. var tokens = tokenize(body, selector);
  226. if (tokens) {
  227. var optimized = optimize(tokens, allowAdjacent);
  228. result = rebuild(optimized);
  229. }
  230. if (!skipCompacting && processableInfo.implementedFor.test(result)) {
  231. result = compact(result);
  232. }
  233. return result;
  234. }
  235. };
  236. };