1
0
mirror of https://github.com/svg/svgo.git synced 2025-08-01 18:46:52 +03:00

Add mergeStyles plugin (#1381)

This commit is contained in:
strarsis
2021-03-27 14:59:56 +01:00
committed by GitHub
parent d89d36eace
commit 19c77d2398
15 changed files with 301 additions and 6 deletions

View File

@ -189,7 +189,10 @@ function csstreeToStyleDeclaration(declaration) {
* @return {string} CSS string or empty array if no styles are set
*/
function getCssStr(elem) {
if (elem.children[0].type === 'text' || elem.children[0].type === 'cdata') {
if (
elem.children.length > 0 &&
(elem.children[0].type === 'text' || elem.children[0].type === 'cdata')
) {
return elem.children[0].value;
}
return '';
@ -203,10 +206,19 @@ function getCssStr(elem) {
* @return {string} reference to field with CSS
*/
function setCssStr(elem, css) {
if (elem.children[0].type === 'text' || elem.children[0].type === 'cdata') {
elem.children[0].value = css;
return elem.children[0].value;
if (elem.children.length === 0) {
elem.children.push({
type: 'text',
value: '',
});
}
if (elem.children[0].type !== 'text' && elem.children[0].type !== 'cdata') {
return css;
}
elem.children[0].value = css;
return css;
}

View File

@ -10,6 +10,7 @@ const pluginsOrder = [
'removeXMLNS',
'removeEditorsNSData',
'cleanupAttrs',
'mergeStyles',
'inlineStyles',
'minifyStyles',
'convertStyleToAttrs',

87
plugins/mergeStyles.js Normal file
View File

@ -0,0 +1,87 @@
'use strict';
const { querySelectorAll, closestByName } = require('../lib/xast.js');
const { getCssStr, setCssStr } = require('../lib/css-tools');
exports.type = 'full';
exports.active = true;
exports.description = 'merge multiple style elements into one';
/**
* Merge multiple style elements into one.
*
* @param {Object} document document element
*
* @author strarsis <strarsis@gmail.com>
*/
exports.fn = function (document) {
// collect <style/>s with valid type attribute (preserve order)
const styleElements = querySelectorAll(document, 'style');
// no <styles/>s, nothing to do
if (styleElements.length === 0) {
return document;
}
const styles = [];
for (const styleElement of styleElements) {
if (
styleElement.attributes.type &&
styleElement.attributes.type !== 'text/css'
) {
// skip <style> with invalid type attribute
continue;
}
if (closestByName(styleElement, 'foreignObject')) {
// skip <foreignObject> content
continue;
}
const cssString = getCssStr(styleElement);
styles.push({
styleElement: styleElement,
mq: styleElement.attributes.media,
cssStr: cssString,
});
}
const collectedStyles = [];
for (let styleNo = 0; styleNo < styles.length; styleNo += 1) {
const style = styles[styleNo];
if (style.mq) {
const wrappedStyles = `@media ${style.mq}{${style.cssStr}}`;
collectedStyles.push(wrappedStyles);
} else {
collectedStyles.push(style.cssStr);
}
// remove all processed style elements except the first one
if (styleNo > 0) {
removeFromParent(style.styleElement);
}
}
const collectedStylesString = collectedStyles.join('');
// combine collected styles in the first style element
const firstStyle = styles[0];
delete firstStyle.styleElement.attributes.media; // remove media mq attribute as CSS media queries are used
if (collectedStylesString.trim().length > 0) {
setCssStr(firstStyle.styleElement, collectedStylesString);
} else {
removeFromParent(firstStyle.styleElement);
}
return document;
};
function removeFromParent(element) {
const parentElement = element.parentNode;
return parentElement.children.splice(
parentElement.children.indexOf(element),
1
);
}

View File

@ -14,6 +14,7 @@ exports.convertPathData = require('./convertPathData.js');
exports.convertShapeToPath = require('./convertShapeToPath.js');
exports.convertStyleToAttrs = require('./convertStyleToAttrs.js');
exports.convertTransform = require('./convertTransform.js');
exports.mergeStyles = require('./mergeStyles.js');
exports.inlineStyles = require('./inlineStyles.js');
exports.mergePaths = require('./mergePaths.js');
exports.minifyStyles = require('./minifyStyles.js');

View File

@ -163,8 +163,8 @@ describe('config', function () {
(item) => item.name === 'cleanupIDs'
);
it('should preserve internal plugins order', () => {
expect(removeAttrsIndex).to.equal(40);
expect(cleanupIDsIndex).to.equal(10);
expect(removeAttrsIndex).to.equal(41);
expect(cleanupIDsIndex).to.equal(11);
});
it('should activate inactive by default plugins', () => {
const removeAttrsPlugin = resolvePluginConfig(

View File

@ -0,0 +1,19 @@
Check whether plugin works with only one style element (no further merging needed, noop).
===
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
.st0{ fill:red; padding-top: 1em; padding-right: 1em; padding-bottom: 1em; padding-left: 1em; }
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>
@@@
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
.st0{ fill:red; padding-top: 1em; padding-right: 1em; padding-bottom: 1em; padding-left: 1em; }
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>

View File

@ -0,0 +1,20 @@
Check whether plugin works with only one style element (no further merging needed, noop) and a media query.
===
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>.st0{ fill:red; padding-top: 1em; padding-right: 1em; padding-bottom: 1em; padding-left: 1em; }</style>
<style>
@media screen and (max-width: 200px) { .st0 { display: none; } }
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>
@@@
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
.st0{ fill:red; padding-top: 1em; padding-right: 1em; padding-bottom: 1em; padding-left: 1em; }@media screen and (max-width: 200px) { .st0 { display: none; } }
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>

View File

@ -0,0 +1,18 @@
Check whether plugin works with merging styles of two style elements (no media queries).
===
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style media="print">.st0{ fill:red; padding-top: 1em; padding-right: 1em; padding-bottom: 1em; padding-left: 1em; }</style>
<style>.test { background: red; }</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>
@@@
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
@media print{.st0{ fill:red; padding-top: 1em; padding-right: 1em; padding-bottom: 1em; padding-left: 1em; }}.test { background: red; }
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>

View File

@ -0,0 +1,19 @@
Check whether plugin works with two style elements that contain styles that also uses media queries.
===
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style media="print">.st0{ fill:red; padding-top: 1em; padding-right: 1em; padding-bottom: 1em; padding-left: 1em; }</style>
<style>.test { background: red; }</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
<style media="only screen and (min-width: 600px)">.wrapper { color: blue; }</style>
</svg>
@@@
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
@media print{.st0{ fill:red; padding-top: 1em; padding-right: 1em; padding-bottom: 1em; padding-left: 1em; }}.test { background: red; }@media only screen and (min-width: 600px){.wrapper { color: blue; }}
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>

View File

@ -0,0 +1,13 @@
Check whether plugin works with no style elements at all (no merging needed, noop).
===
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" class="st0"/>
</svg>
@@@
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" class="st0"/>
</svg>

View File

@ -0,0 +1,20 @@
Check whether plugin removes empty <style> elements
===
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style></style>
<style>
.st0{ fill:red; padding-top: 1em; padding-right: 1em; padding-bottom: 1em; padding-left: 1em; }
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>
@@@
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
.st0{ fill:red; padding-top: 1em; padding-right: 1em; padding-bottom: 1em; padding-left: 1em; }
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>

View File

@ -0,0 +1,16 @@
Check whether plugin removes empty <style> elements with only empty <style> elements
===
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style></style>
<style>
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>
@@@
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>

View File

@ -0,0 +1,23 @@
Check whether plugin removes empty <style> elements mixed with non-empty <style> elements
===
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style></style>
<style></style>
<style>
.test { color: red; }
</style>
<style></style>
<style></style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>
@@@
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
.test { color: red; }
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>

View File

@ -0,0 +1,31 @@
Check whether plugin removes empty <style> elements mixed with non-empty <style> elements
===
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
.a { fill: blue; }
</style>
<style type="">
.b { fill: green; }
</style>
<style type="text/css">
.c { fill: red; }
</style>
<style type="text/invalid">
.d { fill: blue; }
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>
@@@
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
.a { fill: blue; }.b { fill: green; }.c { fill: red; }
</style>
<style type="text/invalid">
.d { fill: blue; }
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>

View File

@ -0,0 +1,15 @@
Check whether plugin removes one empty <style> element that is also the only <style> element.
===
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
</style>
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>
@@@
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" class="st0" style="stroke-width:3;margin-top:1em;margin-right:1em;margin-bottom:1em;margin-left:1em"/>
</svg>