mirror of
https://github.com/svg/svgo.git
synced 2025-07-31 07:44:22 +03:00
Refactor inlineStyles (#1601)
This is a big one - got rid from another closestByName usage - delegated removing empty defs elements to removeEmptyContainers plugin - got rid from all css-tools usages (most inlineStyles code was there for some reason) - combined a few loops - fixed useMqs option (I would remove it in v3 for simplicity as it seems nobody use it)
This commit is contained in:
@ -1,24 +1,43 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('../lib/types').Specificity} Specificity
|
||||||
|
* @typedef {import('../lib/types').XastElement} XastElement
|
||||||
|
* @typedef {import('../lib/types').XastParent} XastParent
|
||||||
|
*/
|
||||||
|
|
||||||
const csstree = require('css-tree');
|
const csstree = require('css-tree');
|
||||||
const { querySelectorAll, closestByName } = require('../lib/xast.js');
|
// @ts-ignore not defined in @types/csso
|
||||||
const cssTools = require('../lib/css-tools');
|
const specificity = require('csso/lib/restructure/prepare/specificity');
|
||||||
|
const stable = require('stable');
|
||||||
|
const {
|
||||||
|
visitSkip,
|
||||||
|
querySelectorAll,
|
||||||
|
detachNodeFromParent,
|
||||||
|
} = require('../lib/xast.js');
|
||||||
|
|
||||||
|
exports.type = 'visitor';
|
||||||
exports.name = 'inlineStyles';
|
exports.name = 'inlineStyles';
|
||||||
|
|
||||||
exports.type = 'full';
|
|
||||||
|
|
||||||
exports.active = true;
|
exports.active = true;
|
||||||
|
|
||||||
exports.params = {
|
|
||||||
onlyMatchedOnce: true,
|
|
||||||
removeMatchedSelectors: true,
|
|
||||||
useMqs: ['', 'screen'],
|
|
||||||
usePseudos: [''],
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.description = 'inline styles (additional options)';
|
exports.description = 'inline styles (additional options)';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two selector specificities.
|
||||||
|
* extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
|
||||||
|
*
|
||||||
|
* @type {(a: Specificity, b: Specificity) => number}
|
||||||
|
*/
|
||||||
|
const compareSpecificity = (a, b) => {
|
||||||
|
for (var i = 0; i < 4; i += 1) {
|
||||||
|
if (a[i] < b[i]) {
|
||||||
|
return -1;
|
||||||
|
} else if (a[i] > b[i]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves + merges styles from style elements to element styles
|
* Moves + merges styles from style elements to element styles
|
||||||
*
|
*
|
||||||
@ -38,267 +57,323 @@ exports.description = 'inline styles (additional options)';
|
|||||||
* what pseudo-classes/-elements to be used
|
* what pseudo-classes/-elements to be used
|
||||||
* empty string element for all non-pseudo-classes and/or -elements
|
* empty string element for all non-pseudo-classes and/or -elements
|
||||||
*
|
*
|
||||||
* @param {Object} root document element
|
|
||||||
* @param {Object} opts plugin params
|
|
||||||
*
|
|
||||||
* @author strarsis <strarsis@gmail.com>
|
* @author strarsis <strarsis@gmail.com>
|
||||||
|
*
|
||||||
|
* @type {import('../lib/types').Plugin<{
|
||||||
|
* onlyMatchedOnce?: boolean,
|
||||||
|
* removeMatchedSelectors?: boolean,
|
||||||
|
* useMqs?: Array<string>,
|
||||||
|
* usePseudos?: Array<string>
|
||||||
|
* }>}
|
||||||
*/
|
*/
|
||||||
exports.fn = function (root, opts) {
|
exports.fn = (root, params) => {
|
||||||
// collect <style/>s
|
const {
|
||||||
var styleEls = querySelectorAll(root, 'style');
|
onlyMatchedOnce = true,
|
||||||
|
removeMatchedSelectors = true,
|
||||||
|
useMqs = ['', 'screen'],
|
||||||
|
usePseudos = [''],
|
||||||
|
} = params;
|
||||||
|
|
||||||
//no <styles/>s, nothing to do
|
/**
|
||||||
if (styleEls.length === 0) {
|
* @type {Array<{ node: XastElement, parentNode: XastParent, cssAst: csstree.StyleSheet }>}
|
||||||
return root;
|
*/
|
||||||
}
|
const styles = [];
|
||||||
|
/**
|
||||||
|
* @type {Array<{
|
||||||
|
* node: csstree.Selector,
|
||||||
|
* item: csstree.ListItem<csstree.CssNode>,
|
||||||
|
* rule: csstree.Rule,
|
||||||
|
* matchedElements?: Array<XastElement>
|
||||||
|
* }>}
|
||||||
|
*/
|
||||||
|
let selectors = [];
|
||||||
|
|
||||||
var styles = [],
|
return {
|
||||||
selectors = [];
|
element: {
|
||||||
|
enter: (node, parentNode) => {
|
||||||
for (var styleEl of styleEls) {
|
// skip <foreignObject /> content
|
||||||
// values other than the empty string or text/css are not used
|
if (node.name === 'foreignObject') {
|
||||||
if (
|
return visitSkip;
|
||||||
styleEl.attributes.type != null &&
|
|
||||||
styleEl.attributes.type !== '' &&
|
|
||||||
styleEl.attributes.type !== 'text/css'
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// skip empty <style/>s or <foreignObject> content.
|
|
||||||
if (
|
|
||||||
styleEl.children.length === 0 ||
|
|
||||||
closestByName(styleEl, 'foreignObject')
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cssStr = cssTools.getCssStr(styleEl);
|
|
||||||
|
|
||||||
// collect <style/>s and their css ast
|
|
||||||
var cssAst = {};
|
|
||||||
try {
|
|
||||||
cssAst = csstree.parse(cssStr, {
|
|
||||||
parseValue: false,
|
|
||||||
parseCustomProperty: false,
|
|
||||||
});
|
|
||||||
} catch (parseError) {
|
|
||||||
// console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
styles.push({
|
|
||||||
styleEl: styleEl,
|
|
||||||
cssAst: cssAst,
|
|
||||||
});
|
|
||||||
|
|
||||||
selectors = selectors.concat(cssTools.flattenToSelectors(cssAst));
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter for mediaqueries to be used or without any mediaquery
|
|
||||||
var selectorsMq = cssTools.filterByMqs(selectors, opts.useMqs);
|
|
||||||
|
|
||||||
// filter for pseudo elements to be used
|
|
||||||
var selectorsPseudo = cssTools.filterByPseudos(selectorsMq, opts.usePseudos);
|
|
||||||
|
|
||||||
// remove PseudoClass from its SimpleSelector for proper matching
|
|
||||||
cssTools.cleanPseudos(selectorsPseudo);
|
|
||||||
|
|
||||||
// stable sort selectors
|
|
||||||
var sortedSelectors = cssTools.sortSelectors(selectorsPseudo).reverse();
|
|
||||||
|
|
||||||
var selector, selectedEl;
|
|
||||||
|
|
||||||
// match selectors
|
|
||||||
for (selector of sortedSelectors) {
|
|
||||||
var selectorStr = csstree.generate(selector.item.data),
|
|
||||||
selectedEls = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
selectedEls = querySelectorAll(root, selectorStr);
|
|
||||||
} catch (selectError) {
|
|
||||||
// console.warn('Warning: Syntax error when trying to select \n\n' + selectorStr + '\n\n, skipped. Error details: ' + selectError);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedEls.length === 0) {
|
|
||||||
// nothing selected
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
selector.selectedEls = selectedEls;
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply <style/> styles to matched elements
|
|
||||||
for (selector of sortedSelectors) {
|
|
||||||
if (!selector.selectedEls) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
opts.onlyMatchedOnce &&
|
|
||||||
selector.selectedEls !== null &&
|
|
||||||
selector.selectedEls.length > 1
|
|
||||||
) {
|
|
||||||
// skip selectors that match more than once if option onlyMatchedOnce is enabled
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply <style/> to matched elements
|
|
||||||
for (selectedEl of selector.selectedEls) {
|
|
||||||
if (selector.rule === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const styleDeclarationList = csstree.parse(
|
|
||||||
selectedEl.attributes.style == null ? '' : selectedEl.attributes.style,
|
|
||||||
{
|
|
||||||
context: 'declarationList',
|
|
||||||
parseValue: false,
|
|
||||||
}
|
}
|
||||||
);
|
// collect only non-empty <style /> elements
|
||||||
const styleDeclarationItems = new Map();
|
if (node.name !== 'style' || node.children.length === 0) {
|
||||||
csstree.walk(styleDeclarationList, {
|
return;
|
||||||
visit: 'Declaration',
|
|
||||||
enter(node, item) {
|
|
||||||
styleDeclarationItems.set(node.property, item);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// merge declarations
|
|
||||||
csstree.walk(selector.rule, {
|
|
||||||
visit: 'Declaration',
|
|
||||||
enter(ruleDeclaration) {
|
|
||||||
// existing inline styles have higher priority
|
|
||||||
// no inline styles, external styles, external styles used
|
|
||||||
// inline styles, external styles same priority as inline styles, inline styles used
|
|
||||||
// inline styles, external styles higher priority than inline styles, external styles used
|
|
||||||
const matchedItem = styleDeclarationItems.get(
|
|
||||||
ruleDeclaration.property
|
|
||||||
);
|
|
||||||
const ruleDeclarationItem =
|
|
||||||
styleDeclarationList.children.createItem(ruleDeclaration);
|
|
||||||
if (matchedItem == null) {
|
|
||||||
styleDeclarationList.children.append(ruleDeclarationItem);
|
|
||||||
} else if (
|
|
||||||
matchedItem.data.important !== true &&
|
|
||||||
ruleDeclaration.important === true
|
|
||||||
) {
|
|
||||||
styleDeclarationList.children.replace(
|
|
||||||
matchedItem,
|
|
||||||
ruleDeclarationItem
|
|
||||||
);
|
|
||||||
styleDeclarationItems.set(
|
|
||||||
ruleDeclaration.property,
|
|
||||||
ruleDeclarationItem
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
selectedEl.attributes.style = csstree.generate(styleDeclarationList);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
opts.removeMatchedSelectors &&
|
|
||||||
selector.selectedEls !== null &&
|
|
||||||
selector.selectedEls.length > 0
|
|
||||||
) {
|
|
||||||
// clean up matching simple selectors if option removeMatchedSelectors is enabled
|
|
||||||
selector.rule.prelude.children.remove(selector.item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opts.removeMatchedSelectors) {
|
|
||||||
return root; // no further processing required
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean up matched class + ID attribute values
|
|
||||||
for (selector of sortedSelectors) {
|
|
||||||
if (!selector.selectedEls) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
opts.onlyMatchedOnce &&
|
|
||||||
selector.selectedEls !== null &&
|
|
||||||
selector.selectedEls.length > 1
|
|
||||||
) {
|
|
||||||
// skip selectors that match more than once if option onlyMatchedOnce is enabled
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (selectedEl of selector.selectedEls) {
|
|
||||||
// class
|
|
||||||
const classList = new Set(
|
|
||||||
selectedEl.attributes.class == null
|
|
||||||
? null
|
|
||||||
: selectedEl.attributes.class.split(' ')
|
|
||||||
);
|
|
||||||
const firstSubSelector = selector.item.data.children.first();
|
|
||||||
if (firstSubSelector.type === 'ClassSelector') {
|
|
||||||
classList.delete(firstSubSelector.name);
|
|
||||||
}
|
|
||||||
if (classList.size === 0) {
|
|
||||||
delete selectedEl.attributes.class;
|
|
||||||
} else {
|
|
||||||
selectedEl.attributes.class = Array.from(classList).join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID
|
|
||||||
if (firstSubSelector.type === 'IdSelector') {
|
|
||||||
if (selectedEl.attributes.id === firstSubSelector.name) {
|
|
||||||
delete selectedEl.attributes.id;
|
|
||||||
}
|
}
|
||||||
}
|
// values other than the empty string or text/css are not used
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean up now empty elements
|
|
||||||
for (var style of styles) {
|
|
||||||
csstree.walk(style.cssAst, {
|
|
||||||
visit: 'Rule',
|
|
||||||
enter: function (node, item, list) {
|
|
||||||
// clean up <style/> atrules without any rulesets left
|
|
||||||
if (
|
if (
|
||||||
node.type === 'Atrule' &&
|
node.attributes.type != null &&
|
||||||
// only Atrules containing rulesets
|
node.attributes.type !== '' &&
|
||||||
node.block !== null &&
|
node.attributes.type !== 'text/css'
|
||||||
node.block.children.isEmpty()
|
|
||||||
) {
|
) {
|
||||||
list.remove(item);
|
return;
|
||||||
|
}
|
||||||
|
// parse css in style element
|
||||||
|
let cssText = '';
|
||||||
|
for (const child of node.children) {
|
||||||
|
if (child.type === 'text' || child.type === 'cdata') {
|
||||||
|
cssText += child.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @type {null | csstree.CssNode}
|
||||||
|
*/
|
||||||
|
let cssAst = null;
|
||||||
|
try {
|
||||||
|
cssAst = csstree.parse(cssText, {
|
||||||
|
parseValue: false,
|
||||||
|
parseCustomProperty: false,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cssAst.type === 'StyleSheet') {
|
||||||
|
styles.push({ node, parentNode, cssAst });
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect selectors
|
||||||
|
csstree.walk(cssAst, {
|
||||||
|
visit: 'Selector',
|
||||||
|
enter(node, item) {
|
||||||
|
const atrule = this.atrule;
|
||||||
|
const rule = this.rule;
|
||||||
|
if (rule == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip media queries not included into useMqs param
|
||||||
|
let mq = '';
|
||||||
|
if (atrule != null) {
|
||||||
|
mq = atrule.name;
|
||||||
|
if (atrule.prelude != null) {
|
||||||
|
mq += ` ${csstree.generate(atrule.prelude)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (useMqs.includes(mq) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<{
|
||||||
|
* item: csstree.ListItem<csstree.CssNode>,
|
||||||
|
* list: csstree.List<csstree.CssNode>
|
||||||
|
* }>}
|
||||||
|
*/
|
||||||
|
const pseudos = [];
|
||||||
|
if (node.type === 'Selector') {
|
||||||
|
node.children.each((childNode, childItem, childList) => {
|
||||||
|
if (
|
||||||
|
childNode.type === 'PseudoClassSelector' ||
|
||||||
|
childNode.type === 'PseudoElementSelector'
|
||||||
|
) {
|
||||||
|
pseudos.push({ item: childItem, list: childList });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip pseudo classes and pseudo elements not includes into usePseudos param
|
||||||
|
const pseudoSelectors = csstree.generate({
|
||||||
|
type: 'Selector',
|
||||||
|
children: new csstree.List().fromArray(
|
||||||
|
pseudos.map((pseudo) => pseudo.item.data)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
if (usePseudos.includes(pseudoSelectors) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove pseudo classes and elements to allow querySelector match elements
|
||||||
|
// TODO this is not very accurate since some pseudo classes like first-child
|
||||||
|
// are used for selection
|
||||||
|
for (const pseudo of pseudos) {
|
||||||
|
pseudo.list.remove(pseudo.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectors.push({ node, item, rule });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
root: {
|
||||||
|
exit: () => {
|
||||||
|
if (styles.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// stable sort selectors
|
||||||
|
const sortedSelectors = stable(selectors, (a, b) => {
|
||||||
|
const aSpecificity = specificity(a.item.data);
|
||||||
|
const bSpecificity = specificity(b.item.data);
|
||||||
|
return compareSpecificity(aSpecificity, bSpecificity);
|
||||||
|
}).reverse();
|
||||||
|
|
||||||
|
for (const selector of sortedSelectors) {
|
||||||
|
// match selectors
|
||||||
|
const selectorText = csstree.generate(selector.item.data);
|
||||||
|
/**
|
||||||
|
* @type {Array<XastElement>}
|
||||||
|
*/
|
||||||
|
const matchedElements = [];
|
||||||
|
try {
|
||||||
|
for (const node of querySelectorAll(root, selectorText)) {
|
||||||
|
if (node.type === 'element') {
|
||||||
|
matchedElements.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (selectError) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// nothing selected
|
||||||
|
if (matchedElements.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply styles to matched elements
|
||||||
|
// skip selectors that match more than once if option onlyMatchedOnce is enabled
|
||||||
|
if (onlyMatchedOnce && matchedElements.length > 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply <style/> to matched elements
|
||||||
|
for (const selectedEl of matchedElements) {
|
||||||
|
const styleDeclarationList = csstree.parse(
|
||||||
|
selectedEl.attributes.style == null
|
||||||
|
? ''
|
||||||
|
: selectedEl.attributes.style,
|
||||||
|
{
|
||||||
|
context: 'declarationList',
|
||||||
|
parseValue: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (styleDeclarationList.type !== 'DeclarationList') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const styleDeclarationItems = new Map();
|
||||||
|
csstree.walk(styleDeclarationList, {
|
||||||
|
visit: 'Declaration',
|
||||||
|
enter(node, item) {
|
||||||
|
styleDeclarationItems.set(node.property, item);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// merge declarations
|
||||||
|
csstree.walk(selector.rule, {
|
||||||
|
visit: 'Declaration',
|
||||||
|
enter(ruleDeclaration) {
|
||||||
|
// existing inline styles have higher priority
|
||||||
|
// no inline styles, external styles, external styles used
|
||||||
|
// inline styles, external styles same priority as inline styles, inline styles used
|
||||||
|
// inline styles, external styles higher priority than inline styles, external styles used
|
||||||
|
const matchedItem = styleDeclarationItems.get(
|
||||||
|
ruleDeclaration.property
|
||||||
|
);
|
||||||
|
const ruleDeclarationItem =
|
||||||
|
styleDeclarationList.children.createItem(ruleDeclaration);
|
||||||
|
if (matchedItem == null) {
|
||||||
|
styleDeclarationList.children.append(ruleDeclarationItem);
|
||||||
|
} else if (
|
||||||
|
matchedItem.data.important !== true &&
|
||||||
|
ruleDeclaration.important === true
|
||||||
|
) {
|
||||||
|
styleDeclarationList.children.replace(
|
||||||
|
matchedItem,
|
||||||
|
ruleDeclarationItem
|
||||||
|
);
|
||||||
|
styleDeclarationItems.set(
|
||||||
|
ruleDeclaration.property,
|
||||||
|
ruleDeclarationItem
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
selectedEl.attributes.style =
|
||||||
|
csstree.generate(styleDeclarationList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
removeMatchedSelectors &&
|
||||||
|
matchedElements.length !== 0 &&
|
||||||
|
selector.rule.prelude.type === 'SelectorList'
|
||||||
|
) {
|
||||||
|
// clean up matching simple selectors if option removeMatchedSelectors is enabled
|
||||||
|
selector.rule.prelude.children.remove(selector.item);
|
||||||
|
}
|
||||||
|
selector.matchedElements = matchedElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no further processing required
|
||||||
|
if (removeMatchedSelectors === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up <style/> rulesets without any css selectors left
|
// clean up matched class + ID attribute values
|
||||||
if (node.type === 'Rule' && node.prelude.children.isEmpty()) {
|
for (const selector of sortedSelectors) {
|
||||||
list.remove(item);
|
if (selector.matchedElements == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlyMatchedOnce && selector.matchedElements.length > 1) {
|
||||||
|
// skip selectors that match more than once if option onlyMatchedOnce is enabled
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const selectedEl of selector.matchedElements) {
|
||||||
|
// class
|
||||||
|
const classList = new Set(
|
||||||
|
selectedEl.attributes.class == null
|
||||||
|
? null
|
||||||
|
: selectedEl.attributes.class.split(' ')
|
||||||
|
);
|
||||||
|
const firstSubSelector = selector.node.children.first();
|
||||||
|
if (
|
||||||
|
firstSubSelector != null &&
|
||||||
|
firstSubSelector.type === 'ClassSelector'
|
||||||
|
) {
|
||||||
|
classList.delete(firstSubSelector.name);
|
||||||
|
}
|
||||||
|
if (classList.size === 0) {
|
||||||
|
delete selectedEl.attributes.class;
|
||||||
|
} else {
|
||||||
|
selectedEl.attributes.class = Array.from(classList).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID
|
||||||
|
if (
|
||||||
|
firstSubSelector != null &&
|
||||||
|
firstSubSelector.type === 'IdSelector'
|
||||||
|
) {
|
||||||
|
if (selectedEl.attributes.id === firstSubSelector.name) {
|
||||||
|
delete selectedEl.attributes.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const style of styles) {
|
||||||
|
csstree.walk(style.cssAst, {
|
||||||
|
visit: 'Rule',
|
||||||
|
enter: function (node, item, list) {
|
||||||
|
// clean up <style/> rulesets without any css selectors left
|
||||||
|
if (
|
||||||
|
node.type === 'Rule' &&
|
||||||
|
node.prelude.type === 'SelectorList' &&
|
||||||
|
node.prelude.children.isEmpty()
|
||||||
|
) {
|
||||||
|
list.remove(item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (style.cssAst.children.isEmpty()) {
|
||||||
|
// remove emtpy style element
|
||||||
|
detachNodeFromParent(style.node, style.parentNode);
|
||||||
|
} else {
|
||||||
|
// update style element if any styles left
|
||||||
|
const firstChild = style.node.children[0];
|
||||||
|
if (firstChild.type === 'text' || firstChild.type === 'cdata') {
|
||||||
|
firstChild.value = csstree.generate(style.cssAst);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
};
|
||||||
if (style.cssAst.children.isEmpty()) {
|
|
||||||
// clean up now emtpy <style/>s
|
|
||||||
var styleParentEl = style.styleEl.parentNode;
|
|
||||||
styleParentEl.spliceContent(
|
|
||||||
styleParentEl.children.indexOf(style.styleEl),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
styleParentEl.name === 'defs' &&
|
|
||||||
styleParentEl.children.length === 0
|
|
||||||
) {
|
|
||||||
// also clean up now empty <def/>s
|
|
||||||
var defsParentEl = styleParentEl.parentNode;
|
|
||||||
defsParentEl.spliceContent(
|
|
||||||
defsParentEl.children.indexOf(styleParentEl),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update existing, left over <style>s
|
|
||||||
cssTools.setCssStr(style.styleEl, csstree.generate(style.cssAst));
|
|
||||||
}
|
|
||||||
|
|
||||||
return root;
|
|
||||||
};
|
};
|
||||||
|
@ -19,10 +19,10 @@
|
|||||||
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 81.285 81.285">
|
<svg id="test" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 81.285 81.285">
|
||||||
<defs>
|
<defs>
|
||||||
<style>
|
<style>
|
||||||
@media only screen and (min-device-width:320px) and (max-device-width:480px) and (-webkit-min-device-pixel-ratio:2){.blue{fill:blue}}
|
@media only screen and (min-device-width:320px) and (max-device-width:480px) and (-webkit-min-device-pixel-ratio:2){}
|
||||||
</style>
|
</style>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="100" height="100" class="blue"/>
|
<rect width="100" height="100" style="fill:blue"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
@@@
|
@@@
|
||||||
|
Before Width: | Height: | Size: 896 B After Width: | Height: | Size: 885 B |
@ -25,6 +25,7 @@
|
|||||||
@@@
|
@@@
|
||||||
|
|
||||||
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 222 57.28">
|
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 222 57.28">
|
||||||
|
<defs/>
|
||||||
<title>button</title>
|
<title>button</title>
|
||||||
<rect width="222" height="57.28" rx="28.64" ry="28.64" style="stroke:red;fill:#37d0cd"/>
|
<rect width="222" height="57.28" rx="28.64" ry="28.64" style="stroke:red;fill:#37d0cd"/>
|
||||||
<path d="M312.75,168.66A2.15,2.15,0,0,1,311.2,165L316,160l-4.8-5a2.15,2.15,0,1,1,3.1-3l6.21,6.49a2.15,2.15,0,0,1,0,3L314.31,168a2.14,2.14,0,0,1-1.56.67Zm0,0" transform="translate(-119 -131.36)" style="fill:#fff"/>
|
<path d="M312.75,168.66A2.15,2.15,0,0,1,311.2,165L316,160l-4.8-5a2.15,2.15,0,1,1,3.1-3l6.21,6.49a2.15,2.15,0,0,1,0,3L314.31,168a2.14,2.14,0,0,1-1.56.67Zm0,0" transform="translate(-119 -131.36)" style="fill:#fff"/>
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@ -29,7 +29,6 @@
|
|||||||
"plugins/removeDimensions.js",
|
"plugins/removeDimensions.js",
|
||||||
"plugins/removeNonInheritableGroupAttrs.js",
|
"plugins/removeNonInheritableGroupAttrs.js",
|
||||||
"plugins/removeXMLNS.js",
|
"plugins/removeXMLNS.js",
|
||||||
"plugins/inlineStyles.js",
|
|
||||||
"plugins/preset-default.js"
|
"plugins/preset-default.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user