UNPKG

svgo

Version:

SVGO is a Node.js library and command-line application for optimizing vector images.

156 lines (139 loc) 5.07 kB
import * as csso from 'csso'; import { detachNodeFromParent } from '../lib/xast.js'; import { hasScripts } from '../lib/svgo/tools.js'; /** * @typedef Usage * @property {boolean=} force * @property {boolean=} ids * @property {boolean=} classes * @property {boolean=} tags * * @typedef MinifyStylesParams * @property {boolean=} restructure Disable or enable a structure optimizations. * @property {boolean=} forceMediaMerge * Enables merging of `@media` rules with the same media query split by other * rules. Unsafe in general, but should work fine in most cases. Use it on * your own risk. * @property {'exclamation' | 'first-exclamation' | boolean=} comments * Specify what comments to leave: * - `'exclamation'` or `true` — leave all exclamation comments * - `'first-exclamation'` — remove every comment except first one * - `false` — remove all comments * @property {boolean | Usage=} usage Advanced optimizations. */ export const name = 'minifyStyles'; export const description = 'minifies styles and removes unused styles'; /** * Minifies styles (<style> element + style attribute) using CSSO. * * @author strarsis <strarsis@gmail.com> * @type {import('../lib/types.js').Plugin<MinifyStylesParams>} */ export const fn = (_root, { usage, ...params }) => { /** @type {Map<import('../lib/types.js').XastElement, import('../lib/types.js').XastParent>} */ const styleElements = new Map(); /** @type {import('../lib/types.js').XastElement[]} */ const elementsWithStyleAttributes = []; /** @type {Set<string>} */ const tagsUsage = new Set(); /** @type {Set<string>} */ const idsUsage = new Set(); /** @type {Set<string>} */ const classesUsage = new Set(); let enableTagsUsage = true; let enableIdsUsage = true; let enableClassesUsage = true; /** * Force to use usage data even if it unsafe. For example, the document * contains scripts or in attributes. */ let forceUsageDeoptimized = false; if (typeof usage === 'boolean') { enableTagsUsage = usage; enableIdsUsage = usage; enableClassesUsage = usage; } else if (usage) { enableTagsUsage = usage.tags == null ? true : usage.tags; enableIdsUsage = usage.ids == null ? true : usage.ids; enableClassesUsage = usage.classes == null ? true : usage.classes; forceUsageDeoptimized = usage.force == null ? false : usage.force; } let deoptimized = false; return { element: { enter: (node, parentNode) => { // detect deoptimizations if (hasScripts(node)) { deoptimized = true; } // collect tags, ids and classes usage tagsUsage.add(node.name); if (node.attributes.id != null) { idsUsage.add(node.attributes.id); } if (node.attributes.class != null) { for (const className of node.attributes.class.split(/\s+/)) { classesUsage.add(className); } } // collect style elements or elements with style attribute if (node.name === 'style' && node.children.length !== 0) { styleElements.set(node, parentNode); } else if (node.attributes.style != null) { elementsWithStyleAttributes.push(node); } }, }, root: { exit: () => { /** @type {csso.Usage} */ const cssoUsage = {}; if (!deoptimized || forceUsageDeoptimized) { if (enableTagsUsage) { cssoUsage.tags = Array.from(tagsUsage); } if (enableIdsUsage) { cssoUsage.ids = Array.from(idsUsage); } if (enableClassesUsage) { cssoUsage.classes = Array.from(classesUsage); } } // minify style elements for (const [styleNode, styleNodeParent] of styleElements.entries()) { if ( styleNode.children[0].type === 'text' || styleNode.children[0].type === 'cdata' ) { const cssText = styleNode.children[0].value; const minified = csso.minify(cssText, { ...params, usage: cssoUsage, }).css; if (minified.length === 0) { detachNodeFromParent(styleNode, styleNodeParent); continue; } // preserve cdata if necessary // TODO split cdata -> text optimization into separate plugin if (cssText.indexOf('>') >= 0 || cssText.indexOf('<') >= 0) { styleNode.children[0].type = 'cdata'; styleNode.children[0].value = minified; } else { styleNode.children[0].type = 'text'; styleNode.children[0].value = minified; } } } // minify style attributes for (const node of elementsWithStyleAttributes) { // style attribute const elemStyle = node.attributes.style; node.attributes.style = csso.minifyBlock(elemStyle, { ...params, }).css; } }, }, }; };