summaryrefslogtreecommitdiff
path: root/node_modules/svgo/plugins/inlineStyles.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/svgo/plugins/inlineStyles.js')
-rw-r--r--node_modules/svgo/plugins/inlineStyles.js270
1 files changed, 135 insertions, 135 deletions
diff --git a/node_modules/svgo/plugins/inlineStyles.js b/node_modules/svgo/plugins/inlineStyles.js
index e931b5f6..1cdcebe7 100644
--- a/node_modules/svgo/plugins/inlineStyles.js
+++ b/node_modules/svgo/plugins/inlineStyles.js
@@ -1,14 +1,13 @@
'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 {
- // @ts-ignore not defined in @types/csso
+ // @ts-ignore internal api
syntax: { specificity },
} = require('csso');
const {
@@ -16,54 +15,32 @@ const {
querySelectorAll,
detachNodeFromParent,
} = require('../lib/xast.js');
+const { compareSpecificity, includesAttrSelector } = require('../lib/style');
+const { attrsGroups, pseudoClasses } = require('./_collections');
exports.name = 'inlineStyles';
exports.description = 'inline styles (additional options)';
/**
- * Compares two selector specificities.
- * extracted from https://github.com/keeganstreet/specificity/blob/main/specificity.js#L211
+ * Some pseudo-classes can only be calculated by clients, like :visited,
+ * :future, or :hover, but there are other pseudo-classes that we can evaluate
+ * during optimization.
*
- * @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;
-};
-
-/**
- * @type {(value: any) => any}
+ * The list of pseudo-classes that we can evaluate during optimization, and so
+ * shouldn't be toggled conditionally through the `usePseudos` parameter.
+ *
+ * @see https://developer.mozilla.org/docs/Web/CSS/Pseudo-classes
*/
-const toAny = (value) => value;
+const preservedPseudos = [
+ ...pseudoClasses.functional,
+ ...pseudoClasses.treeStructural,
+];
/**
- * Moves + merges styles from style elements to element styles
- *
- * Options
- * onlyMatchedOnce (default: true)
- * inline only selectors that match once
- *
- * removeMatchedSelectors (default: true)
- * clean up matched selectors,
- * leave selectors that hadn't matched
- *
- * useMqs (default: ['', 'screen'])
- * what media queries to be used
- * empty string element for styles outside media queries
- *
- * usePseudos (default: [''])
- * what pseudo-classes/-elements to be used
- * empty string element for all non-pseudo-classes and/or -elements
- *
- * @author strarsis <strarsis@gmail.com>
+ * Merges styles from style nodes into inline styles.
*
* @type {import('./plugins-types').Plugin<'inlineStyles'>}
+ * @author strarsis <strarsis@gmail.com>
*/
exports.fn = (root, params) => {
const {
@@ -90,15 +67,12 @@ exports.fn = (root, params) => {
return {
element: {
enter: (node, parentNode) => {
- // skip <foreignObject /> content
if (node.name === 'foreignObject') {
return visitSkip;
}
- // collect only non-empty <style /> elements
if (node.name !== 'style' || node.children.length === 0) {
return;
}
- // values other than the empty string or text/css are not used
if (
node.attributes.type != null &&
node.attributes.type !== '' &&
@@ -106,16 +80,14 @@ exports.fn = (root, params) => {
) {
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}
- */
+
+ const cssText = node.children
+ .filter((child) => child.type === 'text' || child.type === 'cdata')
+ // @ts-ignore
+ .map((child) => child.value)
+ .join('');
+
+ /** @type {?csstree.CssNode} */
let cssAst = null;
try {
cssAst = csstree.parse(cssText, {
@@ -131,63 +103,68 @@ exports.fn = (root, params) => {
// collect selectors
csstree.walk(cssAst, {
- visit: 'Selector',
- enter(node, item) {
+ visit: 'Rule',
+ enter(node) {
const atrule = this.atrule;
- const rule = this.rule;
- if (rule == null) {
- return;
- }
// skip media queries not included into useMqs param
- let mq = '';
+ let mediaQuery = '';
if (atrule != null) {
- mq = atrule.name;
+ mediaQuery = atrule.name;
if (atrule.prelude != null) {
- mq += ` ${csstree.generate(atrule.prelude)}`;
+ mediaQuery += ` ${csstree.generate(atrule.prelude)}`;
}
}
- if (useMqs.includes(mq) === false) {
+ if (!useMqs.includes(mediaQuery)) {
return;
}
- /**
- * @type {Array<{
- * item: csstree.ListItem<csstree.CssNode>,
- * list: csstree.List<csstree.CssNode>
- * }>}
- */
- const pseudos = [];
- if (node.type === 'Selector') {
- node.children.forEach((childNode, childItem, childList) => {
- if (
- childNode.type === 'PseudoClassSelector' ||
- childNode.type === 'PseudoElementSelector'
- ) {
- pseudos.push({ item: childItem, list: childList });
- }
- });
- }
+ if (node.prelude.type === 'SelectorList') {
+ node.prelude.children.forEach((childNode, item) => {
+ if (childNode.type === 'Selector') {
+ /**
+ * @type {Array<{
+ * item: csstree.ListItem<csstree.CssNode>,
+ * list: csstree.List<csstree.CssNode>
+ * }>}
+ */
+ const pseudos = [];
- // 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;
- }
+ childNode.children.forEach(
+ (grandchildNode, grandchildItem, grandchildList) => {
+ const isPseudo =
+ grandchildNode.type === 'PseudoClassSelector' ||
+ grandchildNode.type === 'PseudoElementSelector';
- // 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);
- }
+ if (
+ isPseudo &&
+ !preservedPseudos.includes(grandchildNode.name)
+ ) {
+ pseudos.push({
+ item: grandchildItem,
+ list: grandchildList,
+ });
+ }
+ }
+ );
- selectors.push({ node, item, rule });
+ const pseudoSelectors = csstree.generate({
+ type: 'Selector',
+ children: new csstree.List().fromArray(
+ pseudos.map((pseudo) => pseudo.item.data)
+ ),
+ });
+
+ if (usePseudos.includes(pseudoSelectors)) {
+ for (const pseudo of pseudos) {
+ pseudo.list.remove(pseudo.item);
+ }
+ }
+
+ selectors.push({ node: childNode, rule: node, item: item });
+ }
+ });
+ }
},
});
},
@@ -198,8 +175,8 @@ exports.fn = (root, params) => {
if (styles.length === 0) {
return;
}
- // stable sort selectors
- const sortedSelectors = [...selectors]
+ const sortedSelectors = selectors
+ .slice()
.sort((a, b) => {
const aSpecificity = specificity(a.item.data);
const bSpecificity = specificity(b.item.data);
@@ -210,9 +187,7 @@ exports.fn = (root, params) => {
for (const selector of sortedSelectors) {
// match selectors
const selectorText = csstree.generate(selector.item.data);
- /**
- * @type {Array<XastElement>}
- */
+ /** @type {Array<XastElement>} */
const matchedElements = [];
try {
for (const node of querySelectorAll(root, selectorText)) {
@@ -237,9 +212,7 @@ exports.fn = (root, params) => {
// apply <style/> to matched elements
for (const selectedEl of matchedElements) {
const styleDeclarationList = csstree.parse(
- selectedEl.attributes.style == null
- ? ''
- : selectedEl.attributes.style,
+ selectedEl.attributes.style ?? '',
{
context: 'declarationList',
parseValue: false,
@@ -249,10 +222,18 @@ exports.fn = (root, params) => {
continue;
}
const styleDeclarationItems = new Map();
+
+ /** @type {csstree.ListItem<csstree.CssNode>} */
+ let firstListItem;
+
csstree.walk(styleDeclarationList, {
visit: 'Declaration',
enter(node, item) {
- styleDeclarationItems.set(node.property, item);
+ if (firstListItem == null) {
+ firstListItem = item;
+ }
+
+ styleDeclarationItems.set(node.property.toLowerCase(), item);
},
});
// merge declarations
@@ -263,13 +244,25 @@ exports.fn = (root, params) => {
// 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 property = ruleDeclaration.property;
+
+ if (
+ attrsGroups.presentation.includes(property) &&
+ !selectors.some((selector) =>
+ includesAttrSelector(selector.item, property)
+ )
+ ) {
+ delete selectedEl.attributes[property];
+ }
+
+ const matchedItem = styleDeclarationItems.get(property);
const ruleDeclarationItem =
styleDeclarationList.children.createItem(ruleDeclaration);
if (matchedItem == null) {
- styleDeclarationList.children.append(ruleDeclarationItem);
+ styleDeclarationList.children.insert(
+ ruleDeclarationItem,
+ firstListItem
+ );
} else if (
matchedItem.data.important !== true &&
ruleDeclaration.important === true
@@ -278,15 +271,15 @@ exports.fn = (root, params) => {
matchedItem,
ruleDeclarationItem
);
- styleDeclarationItems.set(
- ruleDeclaration.property,
- ruleDeclarationItem
- );
+ styleDeclarationItems.set(property, ruleDeclarationItem);
}
},
});
- selectedEl.attributes.style =
- csstree.generate(styleDeclarationList);
+
+ const newStyles = csstree.generate(styleDeclarationList);
+ if (newStyles.length !== 0) {
+ selectedEl.attributes.style = newStyles;
+ }
}
if (
@@ -301,7 +294,7 @@ exports.fn = (root, params) => {
}
// no further processing required
- if (removeMatchedSelectors === false) {
+ if (!removeMatchedSelectors) {
return;
}
@@ -323,17 +316,18 @@ exports.fn = (root, params) => {
? null
: selectedEl.attributes.class.split(' ')
);
- /**
- * csstree v2 changed this type
- * @type {csstree.CssNode}
- */
- const firstSubSelector = toAny(selector.node.children.first);
- if (
- firstSubSelector != null &&
- firstSubSelector.type === 'ClassSelector'
- ) {
- classList.delete(firstSubSelector.name);
+
+ for (const child of selector.node.children) {
+ if (
+ child.type === 'ClassSelector' &&
+ !selectors.some((selector) =>
+ includesAttrSelector(selector.item, 'class', child.name, true)
+ )
+ ) {
+ classList.delete(child.name);
+ }
}
+
if (classList.size === 0) {
delete selectedEl.attributes.class;
} else {
@@ -341,13 +335,20 @@ exports.fn = (root, params) => {
}
// ID
+ const firstSubSelector = selector.node.children.first;
if (
- firstSubSelector != null &&
- firstSubSelector.type === 'IdSelector'
+ firstSubSelector?.type === 'IdSelector' &&
+ selectedEl.attributes.id === firstSubSelector.name &&
+ !selectors.some((selector) =>
+ includesAttrSelector(
+ selector.item,
+ 'id',
+ firstSubSelector.name,
+ true
+ )
+ )
) {
- if (selectedEl.attributes.id === firstSubSelector.name) {
- delete selectedEl.attributes.id;
- }
+ delete selectedEl.attributes.id;
}
}
}
@@ -360,8 +361,7 @@ exports.fn = (root, params) => {
if (
node.type === 'Rule' &&
node.prelude.type === 'SelectorList' &&
- // csstree v2 changed this type
- toAny(node.prelude.children.isEmpty)
+ node.prelude.children.isEmpty
) {
list.remove(item);
}
@@ -369,8 +369,8 @@ exports.fn = (root, params) => {
});
// csstree v2 changed this type
- if (toAny(style.cssAst.children.isEmpty)) {
- // remove emtpy style element
+ if (style.cssAst.children.isEmpty) {
+ // remove empty style element
detachNodeFromParent(style.node, style.parentNode);
} else {
// update style element if any styles left