css-rules-minifier.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  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 cssTree */
  21. this.cssRulesMinifier = this.cssRulesMinifier || (() => {
  22. const DEBUG = false;
  23. return {
  24. process: (stylesheets, styles, mediaAllInfo) => {
  25. const stats = { processed: 0, discarded: 0 };
  26. let sheetIndex = 0;
  27. stylesheets.forEach(stylesheetInfo => {
  28. let mediaInfo;
  29. if (stylesheetInfo.media && stylesheetInfo.media != "all") {
  30. mediaInfo = mediaAllInfo.medias.get("style-" + sheetIndex + "-" + stylesheetInfo.media);
  31. } else {
  32. mediaInfo = mediaAllInfo;
  33. }
  34. const cssRules = stylesheetInfo.stylesheet.children;
  35. stats.processed += cssRules.getSize();
  36. stats.discarded += cssRules.getSize();
  37. processRules(cssRules, sheetIndex, mediaInfo);
  38. sheetIndex++;
  39. stats.discarded -= stylesheetInfo.stylesheet.children.getSize();
  40. });
  41. let startTime;
  42. if (DEBUG) {
  43. startTime = Date.now();
  44. log(" -- STARTED processStyleAttribute");
  45. }
  46. styles.forEach(style => processStyleAttribute(style, mediaAllInfo));
  47. if (DEBUG) {
  48. log(" -- ENDED processStyleAttribute delay =", Date.now() - startTime);
  49. }
  50. return stats;
  51. }
  52. };
  53. function processRules(cssRules, sheetIndex, mediaInfo) {
  54. let mediaRuleIndex = 0, startTime;
  55. if (DEBUG && cssRules.length > 1) {
  56. startTime = Date.now();
  57. log(" -- STARTED processRules", "rules.length =", cssRules.children.toArray().length);
  58. }
  59. const removedCssRules = [];
  60. for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
  61. const cssRuleData = cssRule.data;
  62. if (cssRuleData.block && cssRuleData.block.children && cssRuleData.prelude && cssRuleData.prelude.children) {
  63. if (cssRuleData.type == "Atrule" && cssRuleData.name == "media") {
  64. const mediaText = cssTree.generate(cssRuleData.prelude);
  65. processRules(cssRuleData.block.children, sheetIndex, mediaInfo.medias.get("rule-" + sheetIndex + "-" + mediaRuleIndex + "-" + mediaText));
  66. if (!cssRuleData.prelude.children.getSize() || !cssRuleData.block.children.getSize()) {
  67. removedCssRules.push(cssRule);
  68. }
  69. mediaRuleIndex++;
  70. } else if (cssRuleData.type == "Rule") {
  71. const ruleInfo = mediaInfo.rules.get(cssRuleData);
  72. if (!ruleInfo && !mediaInfo.pseudoSelectors.has(cssRuleData)) {
  73. removedCssRules.push(cssRule);
  74. } else if (ruleInfo) {
  75. processRuleInfo(cssRuleData, ruleInfo);
  76. if (!cssRuleData.prelude.children.getSize() || !cssRuleData.block.children.getSize()) {
  77. removedCssRules.push(cssRule);
  78. }
  79. }
  80. }
  81. }
  82. }
  83. removedCssRules.forEach(cssRule => cssRules.remove(cssRule));
  84. if (DEBUG && cssRules.length > 1) {
  85. log(" -- ENDED processRules delay =", Date.now() - startTime);
  86. }
  87. }
  88. function processRuleInfo(cssRule, ruleInfo) {
  89. const removedDeclarations = [];
  90. const removedSelectors = [];
  91. for (let declaration = cssRule.block.children.head; declaration; declaration = declaration.next) {
  92. if (!ruleInfo.style.get(declaration.data.property)) {
  93. removedDeclarations.push(declaration);
  94. }
  95. }
  96. for (let selector = cssRule.prelude.children.head; selector; selector = selector.next) {
  97. if (!ruleInfo.matchedSelectors.has(cssTree.generate(selector.data))) {
  98. removedSelectors.push(selector);
  99. }
  100. }
  101. removedDeclarations.forEach(declaration => cssRule.block.children.remove(declaration));
  102. removedSelectors.forEach(selector => cssRule.prelude.children.remove(selector));
  103. }
  104. function processStyleAttribute(cssStyle, mediaAllInfo) {
  105. const removedDeclarations = [];
  106. const styleInfos = mediaAllInfo.matchedStyles.get(cssStyle);
  107. if (styleInfos) {
  108. let propertyFound;
  109. for (let declaration = cssStyle.children.head; declaration && !propertyFound; declaration = declaration.next) {
  110. if (!styleInfos.style.get(declaration.data.property)) {
  111. removedDeclarations.push(declaration);
  112. }
  113. }
  114. removedDeclarations.forEach(declaration => cssStyle.children.remove(declaration));
  115. }
  116. }
  117. function log(...args) {
  118. console.log("S-File <css-min>", ...args); // eslint-disable-line no-console
  119. }
  120. })();