remove-unused-at-rules.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. var populateComponents = require('./properties/populate-components');
  2. var wrapForOptimizing = require('../wrap-for-optimizing').single;
  3. var restoreFromOptimizing = require('../restore-from-optimizing');
  4. var Token = require('../../tokenizer/token');
  5. var animationNameRegex = /^(\-moz\-|\-o\-|\-webkit\-)?animation-name$/;
  6. var animationRegex = /^(\-moz\-|\-o\-|\-webkit\-)?animation$/;
  7. var keyframeRegex = /^@(\-moz\-|\-o\-|\-webkit\-)?keyframes /;
  8. var importantRegex = /\s{0,31}!important$/;
  9. var optionalMatchingQuotesRegex = /^(['"]?)(.*)\1$/;
  10. function normalize(value) {
  11. return value
  12. .replace(optionalMatchingQuotesRegex, '$2')
  13. .replace(importantRegex, '');
  14. }
  15. function removeUnusedAtRules(tokens, context) {
  16. removeUnusedAtRule(tokens, matchCounterStyle, markCounterStylesAsUsed, context);
  17. removeUnusedAtRule(tokens, matchFontFace, markFontFacesAsUsed, context);
  18. removeUnusedAtRule(tokens, matchKeyframe, markKeyframesAsUsed, context);
  19. removeUnusedAtRule(tokens, matchNamespace, markNamespacesAsUsed, context);
  20. }
  21. function removeUnusedAtRule(tokens, matchCallback, markCallback, context) {
  22. var atRules = {};
  23. var atRule;
  24. var atRuleTokens;
  25. var atRuleToken;
  26. var zeroAt;
  27. var i, l;
  28. for (i = 0, l = tokens.length; i < l; i++) {
  29. matchCallback(tokens[i], atRules);
  30. }
  31. if (Object.keys(atRules).length === 0) {
  32. return;
  33. }
  34. markUsedAtRules(tokens, markCallback, atRules, context);
  35. for (atRule in atRules) {
  36. atRuleTokens = atRules[atRule];
  37. for (i = 0, l = atRuleTokens.length; i < l; i++) {
  38. atRuleToken = atRuleTokens[i];
  39. zeroAt = atRuleToken[0] == Token.AT_RULE ? 1 : 2;
  40. atRuleToken[zeroAt] = [];
  41. }
  42. }
  43. }
  44. function markUsedAtRules(tokens, markCallback, atRules, context) {
  45. var boundMarkCallback = markCallback(atRules);
  46. var i, l;
  47. for (i = 0, l = tokens.length; i < l; i++) {
  48. switch (tokens[i][0]) {
  49. case Token.RULE:
  50. boundMarkCallback(tokens[i], context);
  51. break;
  52. case Token.NESTED_BLOCK:
  53. markUsedAtRules(tokens[i][2], markCallback, atRules, context);
  54. }
  55. }
  56. }
  57. function matchCounterStyle(token, atRules) {
  58. var match;
  59. if (token[0] == Token.AT_RULE_BLOCK && token[1][0][1].indexOf('@counter-style') === 0) {
  60. match = token[1][0][1].split(' ')[1];
  61. atRules[match] = atRules[match] || [];
  62. atRules[match].push(token);
  63. }
  64. }
  65. function markCounterStylesAsUsed(atRules) {
  66. return function (token, context) {
  67. var property;
  68. var wrappedProperty;
  69. var i, l;
  70. for (i = 0, l = token[2].length; i < l; i++) {
  71. property = token[2][i];
  72. if (property[1][1] == 'list-style') {
  73. wrappedProperty = wrapForOptimizing(property);
  74. populateComponents([wrappedProperty], context.validator, context.warnings);
  75. if (wrappedProperty.components[0].value[0][1] in atRules) {
  76. delete atRules[property[2][1]];
  77. }
  78. restoreFromOptimizing([wrappedProperty]);
  79. }
  80. if (property[1][1] == 'list-style-type' && property[2][1] in atRules) {
  81. delete atRules[property[2][1]];
  82. }
  83. }
  84. };
  85. }
  86. function matchFontFace(token, atRules) {
  87. var property;
  88. var match;
  89. var i, l;
  90. if (token[0] == Token.AT_RULE_BLOCK && token[1][0][1] == '@font-face') {
  91. for (i = 0, l = token[2].length; i < l; i++) {
  92. property = token[2][i];
  93. if (property[1][1] == 'font-family') {
  94. match = normalize(property[2][1].toLowerCase());
  95. atRules[match] = atRules[match] || [];
  96. atRules[match].push(token);
  97. break;
  98. }
  99. }
  100. }
  101. }
  102. function markFontFacesAsUsed(atRules) {
  103. return function (token, context) {
  104. var property;
  105. var wrappedProperty;
  106. var component;
  107. var normalizedMatch;
  108. var i, l;
  109. var j, m;
  110. for (i = 0, l = token[2].length; i < l; i++) {
  111. property = token[2][i];
  112. if (property[1][1] == 'font') {
  113. wrappedProperty = wrapForOptimizing(property);
  114. populateComponents([wrappedProperty], context.validator, context.warnings);
  115. component = wrappedProperty.components[6];
  116. for (j = 0, m = component.value.length; j < m; j++) {
  117. normalizedMatch = normalize(component.value[j][1].toLowerCase());
  118. if (normalizedMatch in atRules) {
  119. delete atRules[normalizedMatch];
  120. }
  121. }
  122. restoreFromOptimizing([wrappedProperty]);
  123. }
  124. if (property[1][1] == 'font-family') {
  125. for (j = 2, m = property.length; j < m; j++) {
  126. normalizedMatch = normalize(property[j][1].toLowerCase());
  127. if (normalizedMatch in atRules) {
  128. delete atRules[normalizedMatch];
  129. }
  130. }
  131. }
  132. }
  133. };
  134. }
  135. function matchKeyframe(token, atRules) {
  136. var match;
  137. if (token[0] == Token.NESTED_BLOCK && keyframeRegex.test(token[1][0][1])) {
  138. match = token[1][0][1].split(' ')[1];
  139. atRules[match] = atRules[match] || [];
  140. atRules[match].push(token);
  141. }
  142. }
  143. function markKeyframesAsUsed(atRules) {
  144. return function (token, context) {
  145. var property;
  146. var wrappedProperty;
  147. var component;
  148. var i, l;
  149. var j, m;
  150. for (i = 0, l = token[2].length; i < l; i++) {
  151. property = token[2][i];
  152. if (animationRegex.test(property[1][1])) {
  153. wrappedProperty = wrapForOptimizing(property);
  154. populateComponents([wrappedProperty], context.validator, context.warnings);
  155. component = wrappedProperty.components[7];
  156. for (j = 0, m = component.value.length; j < m; j++) {
  157. if (component.value[j][1] in atRules) {
  158. delete atRules[component.value[j][1]];
  159. }
  160. }
  161. restoreFromOptimizing([wrappedProperty]);
  162. }
  163. if (animationNameRegex.test(property[1][1])) {
  164. for (j = 2, m = property.length; j < m; j++) {
  165. if (property[j][1] in atRules) {
  166. delete atRules[property[j][1]];
  167. }
  168. }
  169. }
  170. }
  171. };
  172. }
  173. function matchNamespace(token, atRules) {
  174. var match;
  175. if (token[0] == Token.AT_RULE && token[1].indexOf('@namespace') === 0) {
  176. match = token[1].split(' ')[1];
  177. atRules[match] = atRules[match] || [];
  178. atRules[match].push(token);
  179. }
  180. }
  181. function markNamespacesAsUsed(atRules) {
  182. var namespaceRegex = new RegExp(Object.keys(atRules).join('\\\||') + '\\\|', 'g');
  183. return function (token) {
  184. var match;
  185. var scope;
  186. var normalizedMatch;
  187. var i, l;
  188. var j, m;
  189. for (i = 0, l = token[1].length; i < l; i++) {
  190. scope = token[1][i];
  191. match = scope[1].match(namespaceRegex);
  192. for (j = 0, m = match.length; j < m; j++) {
  193. normalizedMatch = match[j].substring(0, match[j].length - 1);
  194. if (normalizedMatch in atRules) {
  195. delete atRules[normalizedMatch];
  196. }
  197. }
  198. }
  199. };
  200. }
  201. module.exports = removeUnusedAtRules;