mergeStyles.js 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. 'use strict';
  2. /**
  3. * @typedef {import('../lib/types').XastElement} XastElement
  4. */
  5. const { visitSkip, detachNodeFromParent } = require('../lib/xast.js');
  6. const JSAPI = require('../lib/svgo/jsAPI.js');
  7. exports.name = 'mergeStyles';
  8. exports.type = 'visitor';
  9. exports.active = true;
  10. exports.description = 'merge multiple style elements into one';
  11. /**
  12. * Merge multiple style elements into one.
  13. *
  14. * @author strarsis <strarsis@gmail.com>
  15. *
  16. * @type {import('../lib/types').Plugin<void>}
  17. */
  18. exports.fn = () => {
  19. /**
  20. * @type {null | XastElement}
  21. */
  22. let firstStyleElement = null;
  23. let collectedStyles = '';
  24. let styleContentType = 'text';
  25. return {
  26. element: {
  27. enter: (node, parentNode) => {
  28. // skip <foreignObject> content
  29. if (node.name === 'foreignObject') {
  30. return visitSkip;
  31. }
  32. // collect style elements
  33. if (node.name !== 'style') {
  34. return;
  35. }
  36. // skip <style> with invalid type attribute
  37. if (
  38. node.attributes.type != null &&
  39. node.attributes.type !== '' &&
  40. node.attributes.type !== 'text/css'
  41. ) {
  42. return;
  43. }
  44. // extract style element content
  45. let css = '';
  46. for (const child of node.children) {
  47. if (child.type === 'text') {
  48. css += child.value;
  49. }
  50. if (child.type === 'cdata') {
  51. styleContentType = 'cdata';
  52. css += child.value;
  53. }
  54. }
  55. // remove empty style elements
  56. if (css.trim().length === 0) {
  57. detachNodeFromParent(node, parentNode);
  58. return;
  59. }
  60. // collect css and wrap with media query if present in attribute
  61. if (node.attributes.media == null) {
  62. collectedStyles += css;
  63. } else {
  64. collectedStyles += `@media ${node.attributes.media}{${css}}`;
  65. delete node.attributes.media;
  66. }
  67. // combine collected styles in the first style element
  68. if (firstStyleElement == null) {
  69. firstStyleElement = node;
  70. } else {
  71. detachNodeFromParent(node, parentNode);
  72. firstStyleElement.children = [
  73. new JSAPI(
  74. { type: styleContentType, value: collectedStyles },
  75. firstStyleElement
  76. ),
  77. ];
  78. }
  79. },
  80. },
  81. };
  82. };