Skip to content

Commit 19c77d2

Browse files
authored
Add mergeStyles plugin (#1381)
1 parent d89d36e commit 19c77d2

15 files changed

+301
-6
lines changed

lib/css-tools.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,10 @@ function csstreeToStyleDeclaration(declaration) {
189189
* @return {string} CSS string or empty array if no styles are set
190190
*/
191191
function getCssStr(elem) {
192-
if (elem.children[0].type === 'text' || elem.children[0].type === 'cdata') {
192+
if (
193+
elem.children.length > 0 &&
194+
(elem.children[0].type === 'text' || elem.children[0].type === 'cdata')
195+
) {
193196
return elem.children[0].value;
194197
}
195198
return '';
@@ -203,10 +206,19 @@ function getCssStr(elem) {
203206
* @return {string} reference to field with CSS
204207
*/
205208
function setCssStr(elem, css) {
206-
if (elem.children[0].type === 'text' || elem.children[0].type === 'cdata') {
207-
elem.children[0].value = css;
208-
return elem.children[0].value;
209+
if (elem.children.length === 0) {
210+
elem.children.push({
211+
type: 'text',
212+
value: '',
213+
});
214+
}
215+
216+
if (elem.children[0].type !== 'text' && elem.children[0].type !== 'cdata') {
217+
return css;
209218
}
219+
220+
elem.children[0].value = css;
221+
210222
return css;
211223
}
212224

lib/svgo/config.js

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const pluginsOrder = [
1010
'removeXMLNS',
1111
'removeEditorsNSData',
1212
'cleanupAttrs',
13+
'mergeStyles',
1314
'inlineStyles',
1415
'minifyStyles',
1516
'convertStyleToAttrs',

plugins/mergeStyles.js

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use strict';
2+
3+
const { querySelectorAll, closestByName } = require('../lib/xast.js');
4+
const { getCssStr, setCssStr } = require('../lib/css-tools');
5+
6+
exports.type = 'full';
7+
exports.active = true;
8+
exports.description = 'merge multiple style elements into one';
9+
10+
/**
11+
* Merge multiple style elements into one.
12+
*
13+
* @param {Object} document document element
14+
*
15+
* @author strarsis <[email protected]>
16+
*/
17+
exports.fn = function (document) {
18+
// collect <style/>s with valid type attribute (preserve order)
19+
const styleElements = querySelectorAll(document, 'style');
20+
21+
// no <styles/>s, nothing to do
22+
if (styleElements.length === 0) {
23+
return document;
24+
}
25+
26+
const styles = [];
27+
for (const styleElement of styleElements) {
28+
if (
29+
styleElement.attributes.type &&
30+
styleElement.attributes.type !== 'text/css'
31+
) {
32+
// skip <style> with invalid type attribute
33+
continue;
34+
}
35+
36+
if (closestByName(styleElement, 'foreignObject')) {
37+
// skip <foreignObject> content
38+
continue;
39+
}
40+
41+
const cssString = getCssStr(styleElement);
42+
43+
styles.push({
44+
styleElement: styleElement,
45+
46+
mq: styleElement.attributes.media,
47+
cssStr: cssString,
48+
});
49+
}
50+
51+
const collectedStyles = [];
52+
for (let styleNo = 0; styleNo < styles.length; styleNo += 1) {
53+
const style = styles[styleNo];
54+
55+
if (style.mq) {
56+
const wrappedStyles = `@media ${style.mq}{${style.cssStr}}`;
57+
collectedStyles.push(wrappedStyles);
58+
} else {
59+
collectedStyles.push(style.cssStr);
60+
}
61+
62+
// remove all processed style elements – except the first one
63+
if (styleNo > 0) {
64+
removeFromParent(style.styleElement);
65+
}
66+
}
67+
const collectedStylesString = collectedStyles.join('');
68+
69+
// combine collected styles in the first style element
70+
const firstStyle = styles[0];
71+
delete firstStyle.styleElement.attributes.media; // remove media mq attribute as CSS media queries are used
72+
if (collectedStylesString.trim().length > 0) {
73+
setCssStr(firstStyle.styleElement, collectedStylesString);
74+
} else {
75+
removeFromParent(firstStyle.styleElement);
76+
}
77+
78+
return document;
79+
};
80+
81+
function removeFromParent(element) {
82+
const parentElement = element.parentNode;
83+
return parentElement.children.splice(
84+
parentElement.children.indexOf(element),
85+
1
86+
);
87+
}

plugins/plugins.js

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ exports.convertPathData = require('./convertPathData.js');
1414
exports.convertShapeToPath = require('./convertShapeToPath.js');
1515
exports.convertStyleToAttrs = require('./convertStyleToAttrs.js');
1616
exports.convertTransform = require('./convertTransform.js');
17+
exports.mergeStyles = require('./mergeStyles.js');
1718
exports.inlineStyles = require('./inlineStyles.js');
1819
exports.mergePaths = require('./mergePaths.js');
1920
exports.minifyStyles = require('./minifyStyles.js');

test/config/_index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ describe('config', function () {
163163
(item) => item.name === 'cleanupIDs'
164164
);
165165
it('should preserve internal plugins order', () => {
166-
expect(removeAttrsIndex).to.equal(40);
167-
expect(cleanupIDsIndex).to.equal(10);
166+
expect(removeAttrsIndex).to.equal(41);
167+
expect(cleanupIDsIndex).to.equal(11);
168168
});
169169
it('should activate inactive by default plugins', () => {
170170
const removeAttrsPlugin = resolvePluginConfig(

test/plugins/mergeStyles.01.svg

+19
Loading

test/plugins/mergeStyles.02.svg

+20
Loading

test/plugins/mergeStyles.03.svg

+18
Loading

test/plugins/mergeStyles.04.svg

+19
Loading

test/plugins/mergeStyles.05.svg

+13
Loading

test/plugins/mergeStyles.06.svg

+20
Loading

test/plugins/mergeStyles.07.svg

+16
Loading

test/plugins/mergeStyles.08.svg

+23
Loading

test/plugins/mergeStyles.09.svg

+31
Loading

test/plugins/mergeStyles.10.svg

+15
Loading

0 commit comments

Comments
 (0)