rules-minifier.js 2.7 KB

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