rules-minifier.js 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. /*
  2. * Copyright 2018 Gildas Lormeau
  3. * contact : gildas.lormeau <at> gmail.com
  4. *
  5. * This file is part of SingleFile.
  6. *
  7. * SingleFile is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Lesser General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * SingleFile is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License
  18. * along with SingleFile. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. this.rulesMinifier = this.rulesMinifier || (() => {
  21. const REGEXP_PSEUDO_CLASSES = /::after|::before|::first-line|::first-letter|:focus|:focus-within|:hover|:link|:visited|:active/gi;
  22. return {
  23. process: doc => {
  24. const rulesCache = {};
  25. const stats = {
  26. processed: 0,
  27. discarded: 0
  28. };
  29. doc.querySelectorAll("style").forEach(style => {
  30. const cssRules = [];
  31. if (style.sheet) {
  32. const processed = style.sheet.cssRules.length;
  33. stats.processed += processed;
  34. processRules(doc, style.sheet.cssRules, cssRules, rulesCache);
  35. const stylesheetContent = cssRules.join("");
  36. style.textContent = stylesheetContent;
  37. stats.discarded += processed - style.sheet.cssRules.length;
  38. }
  39. });
  40. return stats;
  41. }
  42. };
  43. function processRules(doc, rules, cssRules, cache) {
  44. if (rules) {
  45. Array.from(rules).forEach(rule => {
  46. if (rule.media) {
  47. cssRules.push("@media " + Array.prototype.join.call(rule.media, ",") + " {");
  48. processRules(doc, rule.cssRules, cssRules, cache);
  49. cssRules.push("}");
  50. } else if (rule.selectorText) {
  51. const selector = getFilteredSelector(rule.selectorText);
  52. if (selector) {
  53. try {
  54. if (cache[selector] || doc.querySelector(selector)) {
  55. cssRules.push(rule.cssText);
  56. cache[selector] = true;
  57. }
  58. } catch (error) {
  59. cssRules.push(rule.cssText);
  60. }
  61. }
  62. } else {
  63. cssRules.push(rule.cssText);
  64. }
  65. });
  66. }
  67. }
  68. function getFilteredSelector(selector) {
  69. if (selector.match(REGEXP_PSEUDO_CLASSES)) {
  70. let selectors = selector.split(/\s*,\s*/g);
  71. selector = selectors.map(selector => {
  72. const simpleSelectors = selector.split(/\s*[ >~+]\s*/g);
  73. const separators = selector.match(/\s*[ >~+]\s*/g);
  74. return simpleSelectors.map((selector, selectorIndex) => {
  75. while (selector.match(REGEXP_PSEUDO_CLASSES)) {
  76. selector = selector.replace(REGEXP_PSEUDO_CLASSES, "").trim();
  77. }
  78. selector = selector.replace(/:?:[^(]+\(\)/g, "");
  79. if (selector == "") {
  80. selector = "*";
  81. }
  82. return selector + (separators && separators[selectorIndex] ? separators[selectorIndex] : "");
  83. }).join("");
  84. }).join(",");
  85. }
  86. return selector;
  87. }
  88. })();