css-medias-minifier.js 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  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, mediaQueryParser */
  21. this.mediasMinifier = this.mediasMinifier || (() => {
  22. return {
  23. process: stylesheets => {
  24. const stats = { processed: 0, discarded: 0 };
  25. stylesheets.forEach((stylesheet, element) => {
  26. const media = stylesheet.media || "all";
  27. if (matchesMediaType(media, "screen")) {
  28. const removedRules = processRules(stylesheet.stylesheet.children, stats);
  29. removedRules.forEach(({ cssRules, cssRule }) => cssRules.remove(cssRule));
  30. } else {
  31. stylesheets.delete(element);
  32. }
  33. });
  34. return stats;
  35. }
  36. };
  37. function processRules(cssRules, stats, removedRules = []) {
  38. for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
  39. const cssRuleData = cssRule.data;
  40. if (cssRuleData.type == "Atrule" && cssRuleData.name == "media") {
  41. stats.processed++;
  42. if (matchesMediaType(cssTree.generate(cssRuleData.prelude), "screen")) {
  43. processRules(cssRuleData.block.children, stats, removedRules);
  44. } else {
  45. removedRules.push({ cssRules, cssRule });
  46. stats.discarded++;
  47. }
  48. }
  49. }
  50. return removedRules;
  51. }
  52. function flatten(array) {
  53. return array.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
  54. }
  55. function matchesMediaType(mediaText, mediaType) {
  56. const foundMediaTypes = flatten(mediaQueryParser.parseMediaList(mediaText).map(node => getMediaTypes(node, mediaType)));
  57. return foundMediaTypes.find(mediaTypeInfo => (!mediaTypeInfo.not && (mediaTypeInfo.value == mediaType || mediaTypeInfo.value == "all")) || (mediaTypeInfo.not && (mediaTypeInfo.value == "all" || mediaTypeInfo.value != mediaType)));
  58. }
  59. function getMediaTypes(parentNode, mediaType, mediaTypes = []) {
  60. parentNode.nodes.map((node, indexNode) => {
  61. if (node.type == "media-query") {
  62. return getMediaTypes(node, mediaType, mediaTypes);
  63. } else {
  64. if (node.type == "media-type") {
  65. const nodeMediaType = { not: Boolean(indexNode && parentNode.nodes[0].type == "keyword" && parentNode.nodes[0].value == "not"), value: node.value };
  66. if (!mediaTypes.find(mediaType => nodeMediaType.not == mediaType.not && nodeMediaType.value == mediaType.value)) {
  67. mediaTypes.push(nodeMediaType);
  68. }
  69. }
  70. }
  71. });
  72. if (!mediaTypes.length) {
  73. mediaTypes.push({ not: false, value: "all" });
  74. }
  75. return mediaTypes;
  76. }
  77. })();