index.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. var _postcss = require("postcss");
  6. var _alphanumSort = require("alphanum-sort");
  7. var _alphanumSort2 = _interopRequireDefault(_alphanumSort);
  8. var _has = require("has");
  9. var _has2 = _interopRequireDefault(_has);
  10. var _postcssSelectorParser = require("postcss-selector-parser");
  11. var _postcssSelectorParser2 = _interopRequireDefault(_postcssSelectorParser);
  12. var _unquote = require("./lib/unquote");
  13. var _unquote2 = _interopRequireDefault(_unquote);
  14. var _canUnquote = require("./lib/canUnquote");
  15. var _canUnquote2 = _interopRequireDefault(_canUnquote);
  16. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  17. const pseudoElements = ["::before", "::after", "::first-letter", "::first-line"];
  18. function getParsed(selectors, callback) {
  19. return (0, _postcssSelectorParser2.default)(callback).processSync(selectors);
  20. }
  21. function attribute(selector) {
  22. if (selector.value) {
  23. // Join selectors that are split over new lines
  24. selector.value = selector.value.replace(/\\\n/g, "").trim();
  25. if ((0, _canUnquote2.default)(selector.value)) {
  26. selector.value = (0, _unquote2.default)(selector.value);
  27. }
  28. selector.operator = selector.operator.trim();
  29. }
  30. if (!selector.raws) {
  31. selector.raws = {};
  32. }
  33. if (!selector.raws.spaces) {
  34. selector.raws.spaces = {};
  35. }
  36. selector.raws.spaces.attribute = {
  37. before: "",
  38. after: ""
  39. };
  40. selector.raws.spaces.operator = {
  41. before: "",
  42. after: ""
  43. };
  44. selector.raws.spaces.value = {
  45. before: "",
  46. after: selector.insensitive ? " " : ""
  47. };
  48. if (selector.insensitive) {
  49. selector.raws.spaces.insensitive = {
  50. before: "",
  51. after: ""
  52. };
  53. }
  54. selector.attribute = selector.attribute.trim();
  55. }
  56. function combinator(selector) {
  57. const value = selector.value.trim();
  58. selector.value = value.length ? value : " ";
  59. }
  60. const pseudoReplacements = {
  61. ":nth-child": ":first-child",
  62. ":nth-of-type": ":first-of-type",
  63. ":nth-last-child": ":last-child",
  64. ":nth-last-of-type": ":last-of-type"
  65. };
  66. function pseudo(selector) {
  67. const value = selector.value.toLowerCase();
  68. if (selector.nodes.length === 1 && pseudoReplacements[value]) {
  69. const first = selector.at(0);
  70. const one = first.at(0);
  71. if (first.length === 1) {
  72. if (one.value === "1") {
  73. selector.replaceWith(_postcssSelectorParser2.default.pseudo({
  74. value: pseudoReplacements[value]
  75. }));
  76. }
  77. if (one.value.toLowerCase() === "even") {
  78. one.value = "2n";
  79. }
  80. }
  81. if (first.length === 3) {
  82. const two = first.at(1);
  83. const three = first.at(2);
  84. if (one.value.toLowerCase() === "2n" && two.value === "+" && three.value === "1") {
  85. one.value = "odd";
  86. two.remove();
  87. three.remove();
  88. }
  89. }
  90. return;
  91. }
  92. const uniques = [];
  93. selector.walk(child => {
  94. if (child.type === "selector") {
  95. const childStr = String(child);
  96. if (!~uniques.indexOf(childStr)) {
  97. uniques.push(childStr);
  98. } else {
  99. child.remove();
  100. }
  101. }
  102. });
  103. if (~pseudoElements.indexOf(value)) {
  104. selector.value = selector.value.slice(1);
  105. }
  106. }
  107. const tagReplacements = {
  108. from: "0%",
  109. "100%": "to"
  110. };
  111. function tag(selector) {
  112. const value = selector.value.toLowerCase();
  113. if ((0, _has2.default)(tagReplacements, value)) {
  114. selector.value = tagReplacements[value];
  115. }
  116. }
  117. function universal(selector) {
  118. const next = selector.next();
  119. if (next && next.type !== "combinator") {
  120. selector.remove();
  121. }
  122. }
  123. const reducers = {
  124. attribute,
  125. combinator,
  126. pseudo,
  127. tag,
  128. universal
  129. };
  130. exports.default = (0, _postcss.plugin)("postcss-minify-selectors", () => {
  131. return css => {
  132. const cache = {};
  133. css.walkRules(rule => {
  134. const selector = rule.raws.selector && rule.raws.selector.value === rule.selector ? rule.raws.selector.raw : rule.selector;
  135. // If the selector ends with a ':' it is likely a part of a custom mixin,
  136. // so just pass through.
  137. if (selector[selector.length - 1] === ":") {
  138. return;
  139. }
  140. if (cache[selector]) {
  141. rule.selector = cache[selector];
  142. return;
  143. }
  144. const optimizedSelector = getParsed(selector, selectors => {
  145. selectors.nodes = (0, _alphanumSort2.default)(selectors.nodes, { insensitive: true });
  146. const uniqueSelectors = [];
  147. selectors.walk(sel => {
  148. const { type } = sel;
  149. // Trim whitespace around the value
  150. sel.spaces.before = sel.spaces.after = "";
  151. if ((0, _has2.default)(reducers, type)) {
  152. reducers[type](sel);
  153. return;
  154. }
  155. const toString = String(sel);
  156. if (type === "selector" && sel.parent.type !== "pseudo") {
  157. if (!~uniqueSelectors.indexOf(toString)) {
  158. uniqueSelectors.push(toString);
  159. } else {
  160. sel.remove();
  161. }
  162. }
  163. });
  164. });
  165. rule.selector = optimizedSelector;
  166. cache[selector] = optimizedSelector;
  167. });
  168. };
  169. });
  170. module.exports = exports["default"];