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:
@ -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;
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ const pluginsOrder = [
|
||||
'removeXMLNS',
|
||||
'removeEditorsNSData',
|
||||
'cleanupAttrs',
|
||||
'mergeStyles',
|
||||
'inlineStyles',
|
||||
'minifyStyles',
|
||||
'convertStyleToAttrs',
|
||||
|
87
plugins/mergeStyles.js
Normal file
87
plugins/mergeStyles.js
Normal 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
|
||||
);
|
||||
}
|
@ -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');
|
||||
|
@ -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(
|
||||
|
19
test/plugins/mergeStyles.01.svg
Normal file
19
test/plugins/mergeStyles.01.svg
Normal 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>
|
20
test/plugins/mergeStyles.02.svg
Normal file
20
test/plugins/mergeStyles.02.svg
Normal 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>
|
18
test/plugins/mergeStyles.03.svg
Normal file
18
test/plugins/mergeStyles.03.svg
Normal 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>
|
19
test/plugins/mergeStyles.04.svg
Normal file
19
test/plugins/mergeStyles.04.svg
Normal 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>
|
13
test/plugins/mergeStyles.05.svg
Normal file
13
test/plugins/mergeStyles.05.svg
Normal 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>
|
20
test/plugins/mergeStyles.06.svg
Normal file
20
test/plugins/mergeStyles.06.svg
Normal 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>
|
16
test/plugins/mergeStyles.07.svg
Normal file
16
test/plugins/mergeStyles.07.svg
Normal 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>
|
23
test/plugins/mergeStyles.08.svg
Normal file
23
test/plugins/mergeStyles.08.svg
Normal 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>
|
31
test/plugins/mergeStyles.09.svg
Normal file
31
test/plugins/mergeStyles.09.svg
Normal 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>
|
15
test/plugins/mergeStyles.10.svg
Normal file
15
test/plugins/mergeStyles.10.svg
Normal 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>
|
Reference in New Issue
Block a user