css-rules-minifier.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /*
  2. * Copyright 2010-2020 Gildas Lormeau
  3. * contact : gildas.lormeau <at> gmail.com
  4. *
  5. * This file is part of SingleFile.
  6. *
  7. * The code in this file is free software: you can redistribute it and/or
  8. * modify it under the terms of the GNU Affero General Public License
  9. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  10. * of the License, or (at your option) any later version.
  11. *
  12. * The code in this file 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 GNU Affero
  15. * General Public License for more details.
  16. *
  17. * As additional permission under GNU AGPL version 3 section 7, you may
  18. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  19. * AGPL normally required by section 4, provided you include this license
  20. * notice and a URL through which recipients can access the Corresponding
  21. * Source.
  22. */
  23. this.singlefile.lib.modules.cssRulesMinifier = this.singlefile.lib.modules.cssRulesMinifier || (() => {
  24. const singlefile = this.singlefile;
  25. const DEBUG = false;
  26. return {
  27. process
  28. };
  29. function process(stylesheets, styles, mediaAllInfo) {
  30. const stats = { processed: 0, discarded: 0 };
  31. let sheetIndex = 0;
  32. stylesheets.forEach(stylesheetInfo => {
  33. if (!stylesheetInfo.scoped) {
  34. const cssRules = stylesheetInfo.stylesheet.children;
  35. if (cssRules) {
  36. stats.processed += cssRules.getSize();
  37. stats.discarded += cssRules.getSize();
  38. let mediaInfo;
  39. if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != "all") {
  40. mediaInfo = mediaAllInfo.medias.get("style-" + sheetIndex + "-" + stylesheetInfo.mediaText);
  41. } else {
  42. mediaInfo = mediaAllInfo;
  43. }
  44. processRules(cssRules, sheetIndex, mediaInfo);
  45. stats.discarded -= cssRules.getSize();
  46. }
  47. }
  48. sheetIndex++;
  49. });
  50. let startTime;
  51. if (DEBUG) {
  52. startTime = Date.now();
  53. log(" -- STARTED processStyleAttribute");
  54. }
  55. styles.forEach(style => processStyleAttribute(style, mediaAllInfo));
  56. if (DEBUG) {
  57. log(" -- ENDED processStyleAttribute delay =", Date.now() - startTime);
  58. }
  59. return stats;
  60. }
  61. function processRules(cssRules, sheetIndex, mediaInfo) {
  62. const cssTree = singlefile.lib.vendor.cssTree;
  63. let mediaRuleIndex = 0, startTime;
  64. if (DEBUG && cssRules.getSize() > 1) {
  65. startTime = Date.now();
  66. log(" -- STARTED processRules", "rules.length =", cssRules.getSize());
  67. }
  68. const removedCssRules = [];
  69. for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
  70. const ruleData = cssRule.data;
  71. if (ruleData.block && ruleData.block.children && ruleData.prelude && ruleData.prelude.children) {
  72. if (ruleData.type == "Atrule" && ruleData.name == "media") {
  73. const mediaText = cssTree.generate(ruleData.prelude);
  74. processRules(ruleData.block.children, sheetIndex, mediaInfo.medias.get("rule-" + sheetIndex + "-" + mediaRuleIndex + "-" + mediaText));
  75. if (!ruleData.prelude.children.getSize() || !ruleData.block.children.getSize()) {
  76. removedCssRules.push(cssRule);
  77. }
  78. mediaRuleIndex++;
  79. } else if (ruleData.type == "Rule") {
  80. const ruleInfo = mediaInfo.rules.get(ruleData);
  81. const pseudoSelectors = mediaInfo.pseudoRules.get(ruleData);
  82. if (!ruleInfo && !pseudoSelectors) {
  83. removedCssRules.push(cssRule);
  84. } else if (ruleInfo) {
  85. processRuleInfo(ruleData, ruleInfo, pseudoSelectors);
  86. if (!ruleData.prelude.children.getSize() || !ruleData.block.children.getSize()) {
  87. removedCssRules.push(cssRule);
  88. }
  89. }
  90. }
  91. } else {
  92. if (!ruleData || ruleData.type == "Raw" || (ruleData.type == "Rule" && (!ruleData.prelude || ruleData.prelude.type == "Raw"))) {
  93. removedCssRules.push(cssRule);
  94. }
  95. }
  96. }
  97. removedCssRules.forEach(cssRule => cssRules.remove(cssRule));
  98. if (DEBUG && cssRules.getSize() > 1) {
  99. log(" -- ENDED processRules delay =", Date.now() - startTime);
  100. }
  101. }
  102. function processRuleInfo(ruleData, ruleInfo, pseudoSelectors) {
  103. const cssTree = singlefile.lib.vendor.cssTree;
  104. const removedDeclarations = [];
  105. const removedSelectors = [];
  106. let pseudoSelectorFound;
  107. for (let selector = ruleData.prelude.children.head; selector; selector = selector.next) {
  108. const selectorText = cssTree.generate(selector.data);
  109. if (pseudoSelectors && pseudoSelectors.has(selectorText)) {
  110. pseudoSelectorFound = true;
  111. }
  112. if (!ruleInfo.matchedSelectors.has(selectorText) && (!pseudoSelectors || !pseudoSelectors.has(selectorText))) {
  113. removedSelectors.push(selector);
  114. }
  115. }
  116. if (!pseudoSelectorFound) {
  117. for (let declaration = ruleData.block.children.tail; declaration; declaration = declaration.prev) {
  118. if (!ruleInfo.declarations.has(declaration.data)) {
  119. removedDeclarations.push(declaration);
  120. }
  121. }
  122. }
  123. removedDeclarations.forEach(declaration => ruleData.block.children.remove(declaration));
  124. removedSelectors.forEach(selector => ruleData.prelude.children.remove(selector));
  125. }
  126. function processStyleAttribute(styleData, mediaAllInfo) {
  127. const removedDeclarations = [];
  128. const styleInfo = mediaAllInfo.matchedStyles.get(styleData);
  129. if (styleInfo) {
  130. let propertyFound;
  131. for (let declaration = styleData.children.head; declaration && !propertyFound; declaration = declaration.next) {
  132. if (!styleInfo.declarations.has(declaration.data)) {
  133. removedDeclarations.push(declaration);
  134. }
  135. }
  136. removedDeclarations.forEach(declaration => styleData.children.remove(declaration));
  137. }
  138. }
  139. function log(...args) {
  140. console.log("S-File <css-min>", ...args); // eslint-disable-line no-console
  141. }
  142. })();