rules-minifier.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  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. if (style.sheet) {
  31. const processed = style.sheet.cssRules.length;
  32. stats.processed += processed;
  33. style.textContent = processRules(doc, style.sheet.cssRules, rulesCache);
  34. stats.discarded += processed - style.sheet.cssRules.length;
  35. }
  36. });
  37. return stats;
  38. }
  39. };
  40. function processRules(doc, rules, cache) {
  41. let stylesheetContent = "";
  42. if (rules) {
  43. Array.from(rules).forEach(rule => {
  44. if (rule.media) {
  45. stylesheetContent += "@media " + Array.prototype.join.call(rule.media, ",") + " {";
  46. stylesheetContent += processRules(doc, rule.cssRules, cache);
  47. stylesheetContent += "}";
  48. } else if (rule.selectorText) {
  49. const selector = getFilteredSelector(rule.selectorText);
  50. if (selector) {
  51. try {
  52. if (cache[selector] || doc.querySelector(selector)) {
  53. stylesheetContent += rule.cssText;
  54. cache[selector] = true;
  55. }
  56. } catch (error) {
  57. stylesheetContent += rule.cssText;
  58. }
  59. }
  60. } else {
  61. stylesheetContent += rule.cssText;
  62. }
  63. });
  64. }
  65. return stylesheetContent;
  66. }
  67. function getFilteredSelector(selector) {
  68. if (selector.match(REGEXP_PSEUDO_CLASSES)) {
  69. let selectors = selector.split(/\s*,\s*/g);
  70. selector = selectors.map(selector => {
  71. const simpleSelectors = selector.split(/\s*[ >~+]\s*/g);
  72. const separators = selector.match(/\s*[ >~+]\s*/g);
  73. return simpleSelectors.map((selector, selectorIndex) => {
  74. while (selector.match(REGEXP_PSEUDO_CLASSES)) {
  75. selector = selector.replace(REGEXP_PSEUDO_CLASSES, "").trim();
  76. }
  77. selector = selector.replace(/:?:[^(]+\(\)/g, "");
  78. if (selector == "") {
  79. selector = "*";
  80. }
  81. return selector + (separators && separators[selectorIndex] ? separators[selectorIndex] : "");
  82. }).join("");
  83. }).join(",");
  84. }
  85. return selector;
  86. }
  87. })();