mirror of
https://github.com/svg/svgo.git
synced 2025-07-31 07:44:22 +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
|
* @return {string} CSS string or empty array if no styles are set
|
||||||
*/
|
*/
|
||||||
function getCssStr(elem) {
|
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 elem.children[0].value;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
@ -203,10 +206,19 @@ function getCssStr(elem) {
|
|||||||
* @return {string} reference to field with CSS
|
* @return {string} reference to field with CSS
|
||||||
*/
|
*/
|
||||||
function setCssStr(elem, css) {
|
function setCssStr(elem, css) {
|
||||||
if (elem.children[0].type === 'text' || elem.children[0].type === 'cdata') {
|
if (elem.children.length === 0) {
|
||||||
elem.children[0].value = css;
|
elem.children.push({
|
||||||
return elem.children[0].value;
|
type: 'text',
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (elem.children[0].type !== 'text' && elem.children[0].type !== 'cdata') {
|
||||||
|
return css;
|
||||||
|
}
|
||||||
|
|
||||||
|
elem.children[0].value = css;
|
||||||
|
|
||||||
return css;
|
return css;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ const pluginsOrder = [
|
|||||||
'removeXMLNS',
|
'removeXMLNS',
|
||||||
'removeEditorsNSData',
|
'removeEditorsNSData',
|
||||||
'cleanupAttrs',
|
'cleanupAttrs',
|
||||||
|
'mergeStyles',
|
||||||
'inlineStyles',
|
'inlineStyles',
|
||||||
'minifyStyles',
|
'minifyStyles',
|
||||||
'convertStyleToAttrs',
|
'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.convertShapeToPath = require('./convertShapeToPath.js');
|
||||||
exports.convertStyleToAttrs = require('./convertStyleToAttrs.js');
|
exports.convertStyleToAttrs = require('./convertStyleToAttrs.js');
|
||||||
exports.convertTransform = require('./convertTransform.js');
|
exports.convertTransform = require('./convertTransform.js');
|
||||||
|
exports.mergeStyles = require('./mergeStyles.js');
|
||||||
exports.inlineStyles = require('./inlineStyles.js');
|
exports.inlineStyles = require('./inlineStyles.js');
|
||||||
exports.mergePaths = require('./mergePaths.js');
|
exports.mergePaths = require('./mergePaths.js');
|
||||||
exports.minifyStyles = require('./minifyStyles.js');
|
exports.minifyStyles = require('./minifyStyles.js');
|
||||||
|
@ -163,8 +163,8 @@ describe('config', function () {
|
|||||||
(item) => item.name === 'cleanupIDs'
|
(item) => item.name === 'cleanupIDs'
|
||||||
);
|
);
|
||||||
it('should preserve internal plugins order', () => {
|
it('should preserve internal plugins order', () => {
|
||||||
expect(removeAttrsIndex).to.equal(40);
|
expect(removeAttrsIndex).to.equal(41);
|
||||||
expect(cleanupIDsIndex).to.equal(10);
|
expect(cleanupIDsIndex).to.equal(11);
|
||||||
});
|
});
|
||||||
it('should activate inactive by default plugins', () => {
|
it('should activate inactive by default plugins', () => {
|
||||||
const removeAttrsPlugin = resolvePluginConfig(
|
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