diff options
Diffstat (limited to 'node_modules/svgo/plugins/inlineStyles.js')
| -rw-r--r-- | node_modules/svgo/plugins/inlineStyles.js | 390 |
1 files changed, 0 insertions, 390 deletions
diff --git a/node_modules/svgo/plugins/inlineStyles.js b/node_modules/svgo/plugins/inlineStyles.js deleted file mode 100644 index e09988de..00000000 --- a/node_modules/svgo/plugins/inlineStyles.js +++ /dev/null @@ -1,390 +0,0 @@ -'use strict'; - -/** - * @typedef {import('../lib/types').XastElement} XastElement - * @typedef {import('../lib/types').XastParent} XastParent - */ - -const csstree = require('css-tree'); -const { - syntax: { specificity }, -} = require('csso'); -const { - visitSkip, - 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)'; - -/** - * 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. - * - * 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 preservedPseudos = [ - ...pseudoClasses.functional, - ...pseudoClasses.treeStructural, -]; - -/** - * Merges styles from style nodes into inline styles. - * - * @type {import('./plugins-types').Plugin<'inlineStyles'>} - * @author strarsis <strarsis@gmail.com> - */ -exports.fn = (root, params) => { - const { - onlyMatchedOnce = true, - removeMatchedSelectors = true, - useMqs = ['', 'screen'], - usePseudos = [''], - } = params; - - /** - * @type {{ node: XastElement, parentNode: XastParent, cssAst: csstree.StyleSheet }[]} - */ - const styles = []; - /** - * @type {{ - * node: csstree.Selector, - * item: csstree.ListItem<csstree.CssNode>, - * rule: csstree.Rule, - * matchedElements?: XastElement[] - * }[]} - */ - let selectors = []; - - return { - element: { - enter: (node, parentNode) => { - if (node.name === 'foreignObject') { - return visitSkip; - } - if (node.name !== 'style' || node.children.length === 0) { - return; - } - if ( - node.attributes.type != null && - node.attributes.type !== '' && - node.attributes.type !== 'text/css' - ) { - return; - } - - 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, { - parseValue: false, - parseCustomProperty: false, - }); - } catch { - return; - } - if (cssAst.type === 'StyleSheet') { - styles.push({ node, parentNode, cssAst }); - } - - // collect selectors - csstree.walk(cssAst, { - visit: 'Rule', - enter(node) { - const atrule = this.atrule; - - // skip media queries not included into useMqs param - let mediaQuery = ''; - if (atrule != null) { - mediaQuery = atrule.name; - if (atrule.prelude != null) { - mediaQuery += ` ${csstree.generate(atrule.prelude)}`; - } - } - if (!useMqs.includes(mediaQuery)) { - return; - } - - if (node.prelude.type === 'SelectorList') { - node.prelude.children.forEach((childNode, item) => { - if (childNode.type === 'Selector') { - /** - * @type {{ - * item: csstree.ListItem<csstree.CssNode>, - * list: csstree.List<csstree.CssNode> - * }[]} - */ - const pseudos = []; - - childNode.children.forEach( - (grandchildNode, grandchildItem, grandchildList) => { - const isPseudo = - grandchildNode.type === 'PseudoClassSelector' || - grandchildNode.type === 'PseudoElementSelector'; - - if ( - isPseudo && - !preservedPseudos.includes(grandchildNode.name) - ) { - pseudos.push({ - item: grandchildItem, - list: grandchildList, - }); - } - }, - ); - - 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 }); - } - }); - } - }, - }); - }, - }, - - root: { - exit: () => { - if (styles.length === 0) { - return; - } - const sortedSelectors = selectors - .slice() - .sort((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 {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 ?? '', - { - context: 'declarationList', - parseValue: false, - }, - ); - if (styleDeclarationList.type !== 'DeclarationList') { - continue; - } - const styleDeclarationItems = new Map(); - - /** @type {csstree.ListItem<csstree.CssNode>} */ - let firstListItem; - - csstree.walk(styleDeclarationList, { - visit: 'Declaration', - enter(node, item) { - if (firstListItem == null) { - firstListItem = item; - } - - styleDeclarationItems.set(node.property.toLowerCase(), 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 property = ruleDeclaration.property; - - if ( - attrsGroups.presentation.has(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.insert( - ruleDeclarationItem, - firstListItem, - ); - } else if ( - matchedItem.data.important !== true && - ruleDeclaration.important === true - ) { - styleDeclarationList.children.replace( - matchedItem, - ruleDeclarationItem, - ); - styleDeclarationItems.set(property, ruleDeclarationItem); - } - }, - }); - - const newStyles = csstree.generate(styleDeclarationList); - if (newStyles.length !== 0) { - selectedEl.attributes.style = newStyles; - } - } - - 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) { - return; - } - - // clean up matched class + ID attribute values - for (const selector of sortedSelectors) { - 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(' '), - ); - - 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 { - selectedEl.attributes.class = Array.from(classList).join(' '); - } - - // ID - const firstSubSelector = selector.node.children.first; - if ( - firstSubSelector?.type === 'IdSelector' && - selectedEl.attributes.id === firstSubSelector.name && - !selectors.some((selector) => - includesAttrSelector( - selector.item, - 'id', - firstSubSelector.name, - true, - ), - ) - ) { - 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); - } - }, - }); - - // csstree v2 changed this type - if (style.cssAst.children.isEmpty) { - // remove empty 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); - } - } - } - }, - }, - }; -}; |
