css-medias-minifier.js 3.0 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. /* global CSSRule, mediaQueryParser */
  21. this.mediasMinifier = this.mediasMinifier || (() => {
  22. return {
  23. process: doc => {
  24. const stats = { processed: 0, discarded: 0 };
  25. doc.querySelectorAll("style").forEach(styleElement => {
  26. if (styleElement.sheet) {
  27. styleElement.textContent = processRules(doc, styleElement.sheet.cssRules, styleElement.media || "all", stats);
  28. }
  29. });
  30. return stats;
  31. }
  32. };
  33. function processRules(doc, cssRules, media, stats) {
  34. let sheetContent = "";
  35. if (matchesMediaType(media, "screen")) {
  36. Array.from(cssRules).forEach(cssRule => {
  37. if (cssRule.type == CSSRule.MEDIA_RULE) {
  38. stats.processed++;
  39. if (matchesMediaType(cssRule.media.mediaText, "screen")) {
  40. sheetContent += "@media " + Array.from(cssRule.media).join(",") + "{";
  41. sheetContent += processRules(doc, cssRule.cssRules, cssRule.media.mediaText, stats);
  42. sheetContent += "}";
  43. } else {
  44. stats.discarded++;
  45. }
  46. } else {
  47. sheetContent += cssRule.cssText;
  48. }
  49. });
  50. }
  51. return sheetContent;
  52. }
  53. function flatten(array) {
  54. return array.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
  55. }
  56. function matchesMediaType(mediaText, mediaType) {
  57. const foundMediaTypes = flatten(mediaQueryParser.parseMediaList(mediaText).map(node => getMediaTypes(node, mediaType)));
  58. return foundMediaTypes.find(mediaTypeInfo => (!mediaTypeInfo.not && (mediaTypeInfo.value == mediaType || mediaTypeInfo.value == "all")) || (mediaTypeInfo.not && (mediaTypeInfo.value == "all" || mediaTypeInfo.value != mediaType)));
  59. }
  60. function getMediaTypes(parentNode, mediaType, mediaTypes = []) {
  61. parentNode.nodes.map((node, indexNode) => {
  62. if (node.type == "media-query") {
  63. return getMediaTypes(node, mediaType, mediaTypes);
  64. } else {
  65. if (node.type == "media-type") {
  66. const nodeMediaType = { not: Boolean(indexNode && parentNode.nodes[0].type == "keyword" && parentNode.nodes[0].value == "not"), value: node.value };
  67. if (!mediaTypes.find(mediaType => nodeMediaType.not == mediaType.not && nodeMediaType.value == mediaType.value)) {
  68. mediaTypes.push(nodeMediaType);
  69. }
  70. }
  71. }
  72. });
  73. if (!mediaTypes.length) {
  74. mediaTypes.push({ not: false, value: "all" });
  75. }
  76. return mediaTypes;
  77. }
  78. })();