rules-minifier.js 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  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. /* global CSSRule */
  21. this.rulesMinifier = this.rulesMinifier || (() => {
  22. const REGEXP_PSEUDO_CLASSES = /::after|::before|::first-line|::first-letter|:focus|:focus-within|:hover|:link|:visited|:active/gi;
  23. return {
  24. process: doc => {
  25. const selectorsData = new Set();
  26. const stats = {
  27. processed: 0,
  28. discarded: 0
  29. };
  30. doc.querySelectorAll("style").forEach(style => {
  31. if (style.sheet) {
  32. const processed = style.sheet.cssRules.length;
  33. stats.processed += processed;
  34. style.textContent = processRules(doc, style.sheet.cssRules, selectorsData);
  35. stats.discarded += processed - style.sheet.cssRules.length;
  36. }
  37. });
  38. return stats;
  39. }
  40. };
  41. function processRules(doc, rules, selectorsData) {
  42. let stylesheetContent = "";
  43. if (rules) {
  44. Array.from(rules).forEach(rule => {
  45. if (rule.type == CSSRule.MEDIA_RULE) {
  46. stylesheetContent += "@media " + Array.prototype.join.call(rule.media, ",") + " {";
  47. stylesheetContent += processRules(doc, rule.cssRules, selectorsData);
  48. stylesheetContent += "}";
  49. } else if (rule.type == CSSRule.STYLE_RULE) {
  50. const selector = getFilteredSelector(rule.selectorText);
  51. if (selector) {
  52. try {
  53. if (selectorsData.has(selector) || doc.querySelector(selector)) {
  54. stylesheetContent += rule.cssText;
  55. selectorsData.add(selector);
  56. }
  57. } catch (error) {
  58. stylesheetContent += rule.cssText;
  59. }
  60. }
  61. } else {
  62. stylesheetContent += rule.cssText;
  63. }
  64. });
  65. }
  66. return stylesheetContent;
  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. })();