Socket
Socket
Sign inDemoInstall

svgo

Package Overview
Dependencies
16
Maintainers
4
Versions
99
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.0.2 to 3.0.3

plugins/convertOneStopGradients.js

1

lib/builtin.js

@@ -15,2 +15,3 @@ 'use strict';

require('../plugins/convertEllipseToCircle.js'),
require('../plugins/convertOneStopGradients.js'),
require('../plugins/convertPathData.js'),

@@ -17,0 +18,0 @@ require('../plugins/convertShapeToPath.js'),

8

lib/path.js
'use strict';
const { removeLeadingZero } = require('./svgo/tools');
/**

@@ -69,3 +71,3 @@ * @typedef {import('./types').PathDataItem} PathDataItem

/**
* @type {(string: string, cursor: number) => [number, number | null]}
* @type {(string: string, cursor: number) => [number, ?number]}
*/

@@ -145,3 +147,3 @@ const readNumber = (string, cursor) => {

/**
* @type {null | PathDataCommand}
* @type {?PathDataCommand}
*/

@@ -254,3 +256,3 @@ let command = null;

// remove zero whole from decimal number
return number.toString().replace(/^0\./, '.').replace(/^-0\./, '-.');
return removeLeadingZero(number);
};

@@ -257,0 +259,0 @@

@@ -20,3 +20,3 @@ 'use strict';

* indent: string,
* textContext: null | XastElement,
* textContext: ?XastElement,
* indentLevel: number,

@@ -23,0 +23,0 @@ * }} State

@@ -18,3 +18,3 @@ 'use strict';

const {
// @ts-ignore not defined in @types/csso
// @ts-ignore internal api
syntax: { specificity },

@@ -51,5 +51,3 @@ } = require('csso');

/**
* @type {StylesheetRule[]}
*/
/** @type {StylesheetRule[]} */
const rules = [];

@@ -83,5 +81,3 @@ csstree.walk(ruleNode.prelude, (node) => {

const parseStylesheet = (css, dynamic) => {
/**
* @type {Array<StylesheetRule>}
*/
/** @type {Array<StylesheetRule>} */
const rules = [];

@@ -117,5 +113,3 @@ const ast = csstree.parse(css, {

const parseStyleDeclarations = (css) => {
/**
* @type {Array<StylesheetDeclaration>}
*/
/** @type {Array<StylesheetDeclaration>} */
const declarations = [];

@@ -142,5 +136,3 @@ const ast = csstree.parse(css, {

const computeOwnStyle = (stylesheet, node) => {
/**
* @type {ComputedStyles}
*/
/** @type {ComputedStyles} */
const computedStyle = {};

@@ -205,6 +197,8 @@ const importantStyles = new Map();

/**
* Compares two selector specificities.
* extracted from https://github.com/keeganstreet/specificity/blob/main/specificity.js#L211
* Compares selector specificities.
* Derived from https://github.com/keeganstreet/specificity/blob/8757133ddd2ed0163f120900047ff0f92760b536/specificity.js#L207
*
* @type {(a: Specificity, b: Specificity) => number}
* @param {Specificity} a
* @param {Specificity} b
* @returns {number}
*/

@@ -222,2 +216,3 @@ const compareSpecificity = (a, b) => {

};
exports.compareSpecificity = compareSpecificity;

@@ -228,9 +223,5 @@ /**

const collectStylesheet = (root) => {
/**
* @type {Array<StylesheetRule>}
*/
/** @type {Array<StylesheetRule>} */
const rules = [];
/**
* @type {Map<XastElement, XastParent>}
*/
/** @type {Map<XastElement, XastParent>} */
const parents = new Map();

@@ -237,0 +228,0 @@ visit(root, {

@@ -68,8 +68,16 @@ 'use strict';

const plugins = config.plugins || ['preset-default'];
if (Array.isArray(plugins) === false) {
if (!Array.isArray(plugins)) {
throw Error(
"Invalid plugins list. Provided 'plugins' in config should be an array."
'malformed config, `plugins` property must be an array.\nSee more info here: https://github.com/svg/svgo#configuration'
);
}
const resolvedPlugins = plugins.map(resolvePluginConfig);
const resolvedPlugins = plugins
.filter((plugin) => plugin != null)
.map(resolvePluginConfig);
if (resolvedPlugins.length < plugins.length) {
console.warn(
'Warning: plugins list includes null or undefined elements, these will be ignored.'
);
}
const globalOverrides = {};

@@ -76,0 +84,0 @@ if (config.floatPrecision != null) {

@@ -17,3 +17,3 @@ 'use strict';

for (const plugin of plugins) {
const override = overrides == null ? null : overrides[plugin.name];
const override = overrides?.[plugin.name];
if (override === false) {

@@ -20,0 +20,0 @@ continue;

'use strict';
/**
* @typedef {import('../../lib/types').XastElement} XastElement
* @typedef {import('../types').PathDataCommand} PathDataCommand

@@ -8,2 +9,4 @@ * @typedef {import('../types').DataUri} DataUri

const { attrsGroups } = require('../../plugins/_collections');
/**

@@ -140,1 +143,38 @@ * Encode plain SVG data string into Data URI string.

exports.removeLeadingZero = removeLeadingZero;
/**
* If the current node contains any scripts. This does not check parents or
* children of the node, only the properties and attributes of the node itself.
*
* @param {XastElement} node Current node to check against.
* @returns {boolean} If the current node contains scripts.
*/
const hasScripts = (node) => {
if (node.name === 'script' && node.children.length !== 0) {
return true;
}
if (node.name === 'a') {
const hasJsLinks = Object.entries(node.attributes).some(
([attrKey, attrValue]) =>
(attrKey === 'href' || attrKey.endsWith(':href')) &&
attrValue != null &&
attrValue.trimStart().startsWith('javascript:')
);
if (hasJsLinks) {
return true;
}
}
const eventAttrs = [
...attrsGroups.animationEvent,
...attrsGroups.documentEvent,
...attrsGroups.documentElementEvent,
...attrsGroups.globalEvent,
...attrsGroups.graphicalEvent,
];
return eventAttrs.some((attr) => node.attributes[attr] != null);
};
exports.hasScripts = hasScripts;

@@ -27,3 +27,3 @@ 'use strict';

/**
* @type {(node: XastNode, selector: string) => null | XastChild}
* @type {(node: XastNode, selector: string) => ?XastChild}
*/

@@ -30,0 +30,0 @@ const querySelector = (node, selector) => {

{
"packageManager": "yarn@2.4.3",
"name": "svgo",
"version": "3.0.2",
"version": "3.0.3",
"description": "Nodejs-based tool for optimizing SVG vector graphics files",

@@ -13,3 +13,3 @@ "license": "MIT",

],
"homepage": "https://github.com/svg/svgo",
"homepage": "https://svgo.dev",
"bugs": {

@@ -38,2 +38,7 @@ "url": "https://github.com/svg/svgo/issues"

"url": "https://github.com/TrySound"
},
{
"name": "Seth Falco",
"email": "seth@falco.fun",
"url": "https://falco.fun/"
}

@@ -69,3 +74,4 @@ ],

"test-regression": "node ./test/regression-extract.js && NO_DIFF=1 node ./test/regression.js",
"prepublishOnly": "rm -rf dist && rollup -c"
"prepublishOnly": "rm -rf dist && rollup -c",
"qa": "yarn lint && yarn typecheck && yarn test && yarn test-browser && yarn test-regression"
},

@@ -77,3 +83,3 @@ "prettier": {

"parserOptions": {
"ecmaVersion": "2021"
"ecmaVersion": 2021
},

@@ -116,3 +122,3 @@ "env": {

"css-tree": "^2.2.1",
"csso": "^5.0.5",
"csso": "5.0.5",
"picocolors": "^1.0.0"

@@ -124,8 +130,8 @@ },

"@types/css-tree": "^2.0.0",
"@types/csso": "^5.0.0",
"@types/jest": "^29.1.1",
"@types/csso": "~5.0.3",
"@types/jest": "^29.5.5",
"del": "^6.0.0",
"eslint": "^8.24.0",
"jest": "^29.1.2",
"node-fetch": "^2.6.2",
"jest": "^29.5.5",
"node-fetch": "^2.7.0",
"pixelmatch": "^5.2.1",

@@ -137,5 +143,5 @@ "playwright": "^1.14.1",

"rollup-plugin-terser": "^7.0.2",
"tar-stream": "^2.2.0",
"tar-stream": "^3.1.6",
"typescript": "^4.8.4"
}
}
}

@@ -221,3 +221,2 @@ 'use strict';

documentEvent: [
'onunload',
'onabort',

@@ -227,4 +226,64 @@ 'onerror',

'onscroll',
'onunload',
'onzoom',
],
documentElementEvent: ['oncopy', 'oncut', 'onpaste'],
globalEvent: [
'oncancel',
'oncanplay',
'oncanplaythrough',
'onchange',
'onclick',
'onclose',
'oncuechange',
'ondblclick',
'ondrag',
'ondragend',
'ondragenter',
'ondragleave',
'ondragover',
'ondragstart',
'ondrop',
'ondurationchange',
'onemptied',
'onended',
'onerror',
'onfocus',
'oninput',
'oninvalid',
'onkeydown',
'onkeypress',
'onkeyup',
'onload',
'onloadeddata',
'onloadedmetadata',
'onloadstart',
'onmousedown',
'onmouseenter',
'onmouseleave',
'onmousemove',
'onmouseout',
'onmouseover',
'onmouseup',
'onmousewheel',
'onpause',
'onplay',
'onplaying',
'onprogress',
'onratechange',
'onreset',
'onresize',
'onscroll',
'onseeked',
'onseeking',
'onselect',
'onshow',
'onstalled',
'onsubmit',
'onsuspend',
'ontimeupdate',
'ontoggle',
'onvolumechange',
'onwaiting',
],
filterPrimitive: ['x', 'y', 'width', 'height', 'result'],

@@ -231,0 +290,0 @@ transferFunction: [

@@ -25,3 +25,3 @@ 'use strict';

/**
* @type {null | TransformItem}
* @type {?TransformItem}
*/

@@ -28,0 +28,0 @@ let current = null;

@@ -8,2 +8,3 @@ 'use strict';

const { visitSkip } = require('../lib/xast.js');
const { hasScripts } = require('../lib/svgo/tools');
const { referencesProps } = require('./_collections.js');

@@ -14,3 +15,3 @@

const regReferencesUrl = /\burl\((["'])?#(.+?)\1\)/;
const regReferencesUrl = /\burl\((["'])?#(.+?)\1\)/g;
const regReferencesHref = /^#(.+?)$/;

@@ -91,3 +92,4 @@ const regReferencesBegin = /(\D+)\./;

*
* @type {(currentId: null | Array<number>) => Array<number>}
* @param {?number[]} currentId
* @returns {number[]}
*/

@@ -152,3 +154,3 @@ const generateId = (currentId) => {

/**
* @type {Map<string, Array<{element: XastElement, name: string, value: string }>>}
* @type {Map<string, Array<{element: XastElement, name: string }>>}
*/

@@ -161,7 +163,7 @@ const referencesById = new Map();

enter: (node) => {
if (force == false) {
// deoptimize if style or script elements are present
if (!force) {
// deoptimize if style or scripts are present
if (
(node.name === 'style' || node.name === 'script') &&
node.children.length !== 0
(node.name === 'style' && node.children.length !== 0) ||
hasScripts(node)
) {

@@ -199,9 +201,9 @@ deoptimized = true;

/**
* @type {null | string}
* @type {string[]}
*/
let id = null;
let ids = [];
if (referencesProps.includes(name)) {
const match = value.match(regReferencesUrl);
if (match != null) {
id = match[2]; // url() reference
const matches = value.matchAll(regReferencesUrl);
for (const match of matches) {
ids.push(match[2]); // url() reference
}

@@ -212,3 +214,3 @@ }

if (match != null) {
id = match[1]; // href reference
ids.push(match[1]); // href reference
}

@@ -219,6 +221,6 @@ }

if (match != null) {
id = match[1]; // href reference
ids.push(match[1]); // href reference
}
}
if (id != null) {
for (const id of ids) {
let refs = referencesById.get(id);

@@ -229,3 +231,3 @@ if (refs == null) {

}
refs.push({ element: node, name, value });
refs.push({ element: node, name });
}

@@ -243,9 +245,8 @@ }

/**
* @type {(id: string) => boolean}
**/
* @param {string} id
* @returns {boolean}
*/
const isIdPreserved = (id) =>
preserveIds.has(id) || hasStringPrefix(id, preserveIdPrefixes);
/**
* @type {null | Array<number>}
*/
/** @type {?number[]} */
let currentId = null;

@@ -257,5 +258,3 @@ for (const [id, refs] of referencesById) {

if (minify && isIdPreserved(id) === false) {
/**
* @type {null | string}
*/
/** @type {?string} */
let currentIdString = null;

@@ -265,5 +264,10 @@ do {

currentIdString = getIdString(currentId);
} while (isIdPreserved(currentIdString));
} while (
isIdPreserved(currentIdString) ||
(referencesById.has(currentIdString) &&
nodeById.get(currentIdString) == null)
);
node.attributes.id = currentIdString;
for (const { element, name, value } of refs) {
for (const { element, name } of refs) {
const value = element.attributes[name];
if (value.includes('#')) {

@@ -270,0 +274,0 @@ // replace id in href and url()

@@ -955,2 +955,12 @@ 'use strict';

/**
* Does the same as `Number.prototype.toFixed` but without casting
* the return value to a string.
* @type {(num: number, precision: number) => number}
*/
function toFixed(num, precision) {
const pow = 10 ** precision;
return Math.round(num * pow) / pow;
}
/**
* Decrease accuracy of floating-point numbers

@@ -964,12 +974,10 @@ * in path data keeping a specified number of decimals.

function strongRound(data) {
for (var i = data.length; i-- > 0; ) {
// @ts-ignore
if (data[i].toFixed(precision) != data[i]) {
// @ts-ignore
var rounded = +data[i].toFixed(precision - 1);
const precisionNum = precision || 0;
for (let i = data.length; i-- > 0; ) {
const fixed = toFixed(data[i], precisionNum);
if (fixed !== data[i]) {
const rounded = toFixed(data[i], precisionNum - 1);
data[i] =
// @ts-ignore
+Math.abs(rounded - data[i]).toFixed(precision + 1) >= error
? // @ts-ignore
+data[i].toFixed(precision)
toFixed(Math.abs(rounded - data[i]), precisionNum + 1) >= error
? fixed
: rounded;

@@ -976,0 +984,0 @@ }

@@ -338,15 +338,9 @@ 'use strict';

const js2transform = (transformJS, params) => {
var transformString = '';
const transformString = transformJS
.map((transform) => {
roundTransform(transform, params);
return `${transform.name}(${cleanupOutData(transform.data, params)})`;
})
.join('');
// collect output value string
transformJS.forEach((transform) => {
roundTransform(transform, params);
transformString +=
(transformString && ' ') +
transform.name +
'(' +
cleanupOutData(transform.data, params) +
')';
});
return transformString;

@@ -353,0 +347,0 @@ };

@@ -11,3 +11,3 @@ 'use strict';

const {
// @ts-ignore not defined in @types/csso
// @ts-ignore internal api
syntax: { specificity },

@@ -20,2 +20,3 @@ } = require('csso');

} = require('../lib/xast.js');
const { compareSpecificity } = require('../lib/style');

@@ -26,45 +27,6 @@ exports.name = 'inlineStyles';

/**
* Compares two selector specificities.
* extracted from https://github.com/keeganstreet/specificity/blob/main/specificity.js#L211
* Merges styles from style nodes into inline styles.
*
* @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}
*/
const toAny = (value) => value;
/**
* 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
*
* @type {import('./plugins-types').Plugin<'inlineStyles'>}
* @author strarsis <strarsis@gmail.com>
*
* @type {import('./plugins-types').Plugin<'inlineStyles'>}
*/

@@ -120,3 +82,3 @@ exports.fn = (root, params) => {

/**
* @type {null | csstree.CssNode}
* @type {?csstree.CssNode}
*/

@@ -217,5 +179,3 @@ let cssAst = null;

const selectorText = csstree.generate(selector.item.data);
/**
* @type {Array<XastElement>}
*/
/** @type {Array<XastElement>} */
const matchedElements = [];

@@ -245,5 +205,3 @@ try {

const styleDeclarationList = csstree.parse(
selectedEl.attributes.style == null
? ''
: selectedEl.attributes.style,
selectedEl.attributes.style ?? '',
{

@@ -261,3 +219,3 @@ context: 'declarationList',

enter(node, item) {
styleDeclarationItems.set(node.property, item);
styleDeclarationItems.set(node.property.toLowerCase(), item);
},

@@ -295,4 +253,7 @@ });

});
selectedEl.attributes.style =
csstree.generate(styleDeclarationList);
const newStyles = csstree.generate(styleDeclarationList);
if (newStyles.length !== 0) {
selectedEl.attributes.style = newStyles;
}
}

@@ -334,13 +295,9 @@

);
/**
* 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') {
classList.delete(child.name);
}
}
if (classList.size === 0) {

@@ -353,9 +310,9 @@ delete selectedEl.attributes.class;

// ID
const firstSubSelector = selector.node.children.first;
if (
firstSubSelector != null &&
firstSubSelector.type === 'IdSelector'
firstSubSelector.type === 'IdSelector' &&
selectedEl.attributes.id === firstSubSelector.name
) {
if (selectedEl.attributes.id === firstSubSelector.name) {
delete selectedEl.attributes.id;
}
delete selectedEl.attributes.id;
}

@@ -373,4 +330,3 @@ }

node.prelude.type === 'SelectorList' &&
// csstree v2 changed this type
toAny(node.prelude.children.isEmpty)
node.prelude.children.isEmpty
) {

@@ -383,3 +339,3 @@ list.remove(item);

// csstree v2 changed this type
if (toAny(style.cssAst.children.isEmpty)) {
if (style.cssAst.children.isEmpty) {
// remove emtpy style element

@@ -386,0 +342,0 @@ detachNodeFromParent(style.node, style.parentNode);

@@ -22,3 +22,3 @@ 'use strict';

/**
* @type {null | XastElement}
* @type {?XastElement}
*/

@@ -25,0 +25,0 @@ let firstStyleElement = null;

@@ -5,23 +5,44 @@ 'use strict';

* @typedef {import('../lib/types').XastElement} XastElement
* @typedef {import('../lib/types').XastParent} XastParent
*/
const csso = require('csso');
const { detachNodeFromParent } = require('../lib/xast');
const { hasScripts } = require('../lib/svgo/tools');
exports.name = 'minifyStyles';
exports.description =
'minifies styles and removes unused styles based on usage data';
exports.description = 'minifies styles and removes unused styles';
/**
* Minifies styles (<style> element + style attribute) using CSSO
* Minifies styles (<style> element + style attribute) using CSSO.
*
* @author strarsis <strarsis@gmail.com>
*
* @type {import('./plugins-types').Plugin<'minifyStyles'>}
*/
exports.fn = (_root, { usage, ...params }) => {
/** @type {Map<XastElement, XastParent>} */
const styleElements = new Map();
/** @type {Array<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 (document contains <script> or on* attributes)
/**
* 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') {

@@ -37,36 +58,13 @@ enableTagsUsage = usage;

}
/**
* @type {Array<XastElement>}
*/
const styleElements = [];
/**
* @type {Array<XastElement>}
*/
const elementsWithStyleAttributes = [];
let deoptimized = false;
/**
* @type {Set<string>}
*/
const tagsUsage = new Set();
/**
* @type {Set<string>}
*/
const idsUsage = new Set();
/**
* @type {Set<string>}
*/
const classesUsage = new Set();
return {
element: {
enter: (node) => {
enter: (node, parentNode) => {
// detect deoptimisations
if (node.name === 'script') {
if (hasScripts(node)) {
deoptimized = true;
}
for (const name of Object.keys(node.attributes)) {
if (name.startsWith('on')) {
deoptimized = true;
}
}
// collect tags, ids and classes usage

@@ -84,3 +82,3 @@ tagsUsage.add(node.name);

if (node.name === 'style' && node.children.length !== 0) {
styleElements.push(node);
styleElements.set(node, parentNode);
} else if (node.attributes.style != null) {

@@ -94,14 +92,12 @@ elementsWithStyleAttributes.push(node);

exit: () => {
/**
* @type {csso.Usage}
*/
/** @type {csso.Usage} */
const cssoUsage = {};
if (deoptimized === false || forceUsageDeoptimized === true) {
if (enableTagsUsage && tagsUsage.size !== 0) {
if (!deoptimized || forceUsageDeoptimized) {
if (enableTagsUsage) {
cssoUsage.tags = Array.from(tagsUsage);
}
if (enableIdsUsage && idsUsage.size !== 0) {
if (enableIdsUsage) {
cssoUsage.ids = Array.from(idsUsage);
}
if (enableClassesUsage && classesUsage.size !== 0) {
if (enableClassesUsage) {
cssoUsage.classes = Array.from(classesUsage);

@@ -111,8 +107,8 @@ }

// minify style elements
for (const node of styleElements) {
for (const [styleNode, styleNodeParent] of styleElements.entries()) {
if (
node.children[0].type === 'text' ||
node.children[0].type === 'cdata'
styleNode.children[0].type === 'text' ||
styleNode.children[0].type === 'cdata'
) {
const cssText = node.children[0].value;
const cssText = styleNode.children[0].value;
const minified = csso.minify(cssText, {

@@ -122,10 +118,16 @@ ...params,

}).css;
if (minified.length === 0) {
detachNodeFromParent(styleNode, styleNodeParent);
continue;
}
// preserve cdata if necessary
// TODO split cdata -> text optimisation into separate plugin
if (cssText.indexOf('>') >= 0 || cssText.indexOf('<') >= 0) {
node.children[0].type = 'cdata';
node.children[0].value = minified;
styleNode.children[0].type = 'cdata';
styleNode.children[0].value = minified;
} else {
node.children[0].type = 'text';
node.children[0].value = minified;
styleNode.children[0].type = 'text';
styleNode.children[0].value = minified;
}

@@ -132,0 +134,0 @@ }

@@ -76,6 +76,24 @@ import type {

inlineStyles: {
/**
* Inlines selectors that match once only.
*
* @default true
*/
onlyMatchedOnce?: boolean;
/**
* Clean up matched selectors. Unused selects are left as-is.
*
* @default true
*/
removeMatchedSelectors?: boolean;
useMqs?: Array<string>;
usePseudos?: Array<string>;
/**
* Media queries to use. An empty string indicates all selectors outside of
* media queries.
*/
useMqs?: string[];
/**
* Pseudo-classes and elements to use. An empty string indicates all
* all non-pseudo-classes and elements.
*/
usePseudos?: string[];
};

@@ -93,3 +111,3 @@ mergePaths: {

*/
restructure?: boolean | undefined;
restructure?: boolean;
/**

@@ -100,3 +118,3 @@ * Enables merging of @media rules with the same media query by splitted by other rules.

*/
forceMediaMerge?: boolean | undefined;
forceMediaMerge?: boolean;
/**

@@ -109,3 +127,3 @@ * Specify what comments to leave:

*/
comments?: string | boolean | undefined;
comments?: string | boolean;
/**

@@ -126,3 +144,5 @@ * Advanced optimizations

moveGroupAttrsToElems: void;
removeComments: void;
removeComments: {
preservePatterns: Array<RegExp|string> | false
};
removeDesc: {

@@ -210,2 +230,3 @@ removeAny?: boolean;

};
convertOneStopGradients: void;
convertStyleToAttrs: {

@@ -212,0 +233,0 @@ keepImportant?: boolean;

'use strict';
/**
* @typedef {import('../lib/types.js').PluginInfo} PluginInfo
* @typedef {import('../lib/types').XastElement} XastElement
*/
const csstree = require('css-tree');

@@ -44,19 +49,27 @@ const { referencesProps } = require('./_collections.js');

/**
* prefix an ID
* @type {(prefix: string, name: string) => string}
* Prefix the given string, unless it already starts with the generated prefix.
*
* @param {(id: string) => string} prefixGenerator Function to generate a prefix.
* @param {string} body An arbitrary string.
* @returns {string} The given string with a prefix prepended to it.
*/
const prefixId = (prefix, value) => {
if (value.startsWith(prefix)) {
return value;
const prefixId = (prefixGenerator, body) => {
const prefix = prefixGenerator(body);
if (body.startsWith(prefix)) {
return body;
}
return prefix + value;
return prefix + body;
};
/**
* prefix an #ID
* @type {(prefix: string, name: string) => string | null}
* Insert the prefix in a reference string. A reference string is already
* prefixed with #, so the prefix is inserted after the first character.
*
* @param {(id: string) => string} prefixGenerator Function to generate a prefix.
* @param {string} reference An arbitrary string, should start with "#".
* @returns {?string} The given string with a prefix inserted, or null if the string did not start with "#".
*/
const prefixReference = (prefix, value) => {
if (value.startsWith('#')) {
return '#' + prefixId(prefix, value.slice(1));
const prefixReference = (prefixGenerator, reference) => {
if (reference.startsWith('#')) {
return '#' + prefixId(prefixGenerator, reference.slice(1));
}

@@ -66,5 +79,41 @@ return null;

/** @type {(value: any) => any} */
const toAny = (value) => value;
/**
* Generates a prefix for the given string.
*
* @param {string} body An arbitrary string.
* @param {XastElement} node XML node that the identifier belongs to.
* @param {PluginInfo} info
* @param {((node: XastElement, info: PluginInfo) => string)|string|boolean|undefined} prefixGenerator Some way of obtaining a prefix.
* @param {string} delim Content to insert between the prefix and original value.
* @param {Map<string, string>} history Map of previously generated prefixes to IDs.
* @returns {string} A generated prefix.
*/
const generatePrefix = (body, node, info, prefixGenerator, delim, history) => {
if (typeof prefixGenerator === 'function') {
let prefix = history.get(body);
if (prefix != null) {
return prefix;
}
prefix = prefixGenerator(node, info) + delim;
history.set(body, prefix);
return prefix;
}
if (typeof prefixGenerator === 'string') {
return prefixGenerator + delim;
}
if (prefixGenerator === false) {
return '';
}
if (info.path != null && info.path.length > 0) {
return escapeIdentifierName(getBasename(info.path)) + delim;
}
return 'prefix' + delim;
};
/**

@@ -78,4 +127,12 @@ * Prefixes identifiers

exports.fn = (_root, params, info) => {
const { delim = '__', prefixIds = true, prefixClassNames = true } = params;
const {
delim = '__',
prefix,
prefixIds = true,
prefixClassNames = true,
} = params;
/** @type {Map<string, string>} */
const prefixMap = new Map();
return {

@@ -85,15 +142,7 @@ element: {

/**
* prefix, from file name or option
* @type {string}
* @param {string} id A node identifier or class.
* @returns {string} Given string with a prefix inserted, or null if the string did not start with "#".
*/
let prefix = 'prefix' + delim;
if (typeof params.prefix === 'function') {
prefix = params.prefix(node, info) + delim;
} else if (typeof params.prefix === 'string') {
prefix = params.prefix + delim;
} else if (params.prefix === false) {
prefix = '';
} else if (info.path != null && info.path.length > 0) {
prefix = escapeIdentifierName(getBasename(info.path)) + delim;
}
const prefixGenerator = (id) =>
generatePrefix(id, node, info, prefix, delim, prefixMap);

@@ -116,3 +165,3 @@ // prefix id/class selectors and url() references in styles

/**
* @type {null | csstree.CssNode}
* @type {?csstree.CssNode}
*/

@@ -135,3 +184,3 @@ let cssAst = null;

) {
node.name = prefixId(prefix, node.name);
node.name = prefixId(prefixGenerator, node.name);
return;

@@ -141,9 +190,12 @@ }

// csstree v2 changed this type
if (node.type === 'Url' && toAny(node.value).length > 0) {
// @ts-ignore
if (node.type === 'Url' && node.value.length > 0) {
const prefixed = prefixReference(
prefix,
unquote(toAny(node.value))
prefixGenerator,
// @ts-ignore
unquote(node.value)
);
if (prefixed != null) {
toAny(node).value = prefixed;
// @ts-ignore
node.value = prefixed;
}

@@ -169,3 +221,3 @@ }

) {
node.attributes.id = prefixId(prefix, node.attributes.id);
node.attributes.id = prefixId(prefixGenerator, node.attributes.id);
}

@@ -181,3 +233,3 @@

.split(/\s+/)
.map((name) => prefixId(prefix, name))
.map((name) => prefixId(prefixGenerator, name))
.join(' ');

@@ -193,3 +245,6 @@ }

) {
const prefixed = prefixReference(prefix, node.attributes[name]);
const prefixed = prefixReference(
prefixGenerator,
node.attributes[name]
);
if (prefixed != null) {

@@ -210,3 +265,3 @@ node.attributes[name] = prefixed;

(match, url) => {
const prefixed = prefixReference(prefix, url);
const prefixed = prefixReference(prefixGenerator, url);
if (prefixed == null) {

@@ -230,3 +285,3 @@ return match;

const [id, postfix] = val.split('.');
return `${prefixId(prefix, id)}.${postfix}`;
return `${prefixId(prefixGenerator, id)}.${postfix}`;
}

@@ -233,0 +288,0 @@ return val;

@@ -9,2 +9,8 @@ 'use strict';

/**
* If a comment matches one of the following patterns, it will be
* preserved by default. Particularly for copyright/license information.
*/
const DEFAULT_PRESERVE_PATTERNS = [/^!/];
/**
* Remove comments.

@@ -20,9 +26,25 @@ *

*/
exports.fn = () => {
exports.fn = (_root, params) => {
const { preservePatterns = DEFAULT_PRESERVE_PATTERNS } = params;
return {
comment: {
enter: (node, parentNode) => {
if (node.value.charAt(0) !== '!') {
detachNodeFromParent(node, parentNode);
if (preservePatterns) {
if (!Array.isArray(preservePatterns)) {
throw Error(
`Expected array in removeComments preservePatterns parameter but received ${preservePatterns}`
);
}
const matches = preservePatterns.some((pattern) => {
return new RegExp(pattern).test(node.value);
});
if (matches) {
return;
}
}
detachNodeFromParent(node, parentNode);
},

@@ -29,0 +51,0 @@ },

@@ -22,3 +22,3 @@ 'use strict';

exports.fn = (root, params) => {
const { removeAny = true } = params;
const { removeAny = false } = params;
return {

@@ -25,0 +25,0 @@ element: {

'use strict';
/**
* @typedef {import('../lib/types').XastElement} XastElement
* @typedef {import('../lib/types').XastParent} XastParent
*/
const { elemsGroups, referencesProps } = require('./_collections.js');
const {

@@ -12,2 +18,4 @@ visit,

const nonRendering = elemsGroups.nonRendering;
exports.name = 'removeHiddenElems';

@@ -54,7 +62,16 @@ exports.description =

/**
* Skip non-rendered nodes initially, and only detach if they have no ID, or
* their ID is not referenced by another node.
*
* @type {Map<XastElement, XastParent>}
*/
const nonRenderedNodes = new Map();
visit(root, {
element: {
enter: (node, parentNode) => {
// transparent element inside clipPath still affect clipped elements
if (node.name === 'clipPath') {
// transparent non-rendering elements still apply where referenced
if (nonRendering.includes(node.name)) {
nonRenderedNodes.set(node, parentNode);
return visitSkip;

@@ -310,4 +327,28 @@ }

},
exit: (node, parentNode) => {
if (node.name !== 'svg' || parentNode.type !== 'root') {
return;
}
for (const [
nonRenderedNode,
nonRenderedParent,
] of nonRenderedNodes.entries()) {
if (nonRenderedNode.attributes.id == null) {
detachNodeFromParent(node, nonRenderedParent);
continue;
}
const selector = referencesProps
.map((attr) => `[${attr}="url(#${nonRenderedNode.attributes.id})"]`)
.join(',');
const element = querySelector(root, selector);
if (element == null) {
detachNodeFromParent(node, nonRenderedParent);
}
}
},
},
};
};

@@ -24,3 +24,3 @@ 'use strict';

/**
* @type {null | {
* @type {?{
* top: number,

@@ -27,0 +27,0 @@ * right: number,

@@ -24,3 +24,3 @@ 'use strict';

node.attributes['xlink:href'] != null &&
/(\.|image\/)(jpg|png|gif)/.test(node.attributes['xlink:href'])
/(\.|image\/)(jpe?g|png|gif)/.test(node.attributes['xlink:href'])
) {

@@ -27,0 +27,0 @@ detachNodeFromParent(node, parentNode);

'use strict';
const { detachNodeFromParent } = require('../lib/xast.js');
const { attrsGroups } = require('./_collections.js');
exports.name = 'removeScriptElement';
exports.description = 'removes <script> elements (disabled by default)';
exports.description = 'removes scripts (disabled by default)';
/** Union of all event attributes. */
const eventAttrs = [
...attrsGroups.animationEvent,
...attrsGroups.documentEvent,
...attrsGroups.documentElementEvent,
...attrsGroups.globalEvent,
...attrsGroups.graphicalEvent,
];
/**
* Remove <script>.
* Remove scripts.
*

@@ -14,3 +24,2 @@ * https://www.w3.org/TR/SVG11/script.html

* @author Patrick Klingemann
*
* @type {import('./plugins-types').Plugin<'removeScriptElement'>}

@@ -24,6 +33,40 @@ */

detachNodeFromParent(node, parentNode);
return;
}
for (const attr of eventAttrs) {
if (node.attributes[attr] != null) {
delete node.attributes[attr];
}
}
},
exit: (node, parentNode) => {
if (node.name !== 'a') {
return;
}
for (const attr of Object.keys(node.attributes)) {
if (attr === 'href' || attr.endsWith(':href')) {
if (
node.attributes[attr] == null ||
!node.attributes[attr].trimStart().startsWith('javascript:')
) {
continue;
}
const index = parentNode.children.indexOf(node);
parentNode.children.splice(index, 1, ...node.children);
// TODO remove legacy parentNode in v4
for (const child of node.children) {
Object.defineProperty(child, 'parentNode', {
writable: true,
value: parentNode,
});
}
}
}
},
},
};
};

@@ -185,6 +185,3 @@ 'use strict';

// keep defaults if parent has own or inherited style
if (
computedParentStyle == null ||
computedParentStyle[name] == null
) {
if (computedParentStyle?.[name] == null) {
delete node.attributes[name];

@@ -194,4 +191,3 @@ }

if (uselessOverrides && node.attributes.id == null) {
const style =
computedParentStyle == null ? null : computedParentStyle[name];
const style = computedParentStyle?.[name];
if (

@@ -198,0 +194,0 @@ presentationNonInheritableGroupAttrs.includes(name) === false &&

@@ -5,2 +5,3 @@ 'use strict';

const { collectStylesheet, computeStyle } = require('../lib/style.js');
const { hasScripts } = require('../lib/svgo/tools.js');
const { elemsGroups } = require('./_collections.js');

@@ -30,3 +31,3 @@

enter: (node) => {
if (node.name === 'style' || node.name === 'script') {
if (node.name === 'style' || hasScripts(node)) {
hasStyleOrScript = true;

@@ -33,0 +34,0 @@ }

'use strict';
const { collectStylesheet } = require('../lib/style');
const { detachNodeFromParent, querySelectorAll } = require('../lib/xast');
/**

@@ -23,3 +26,5 @@ * @typedef {import('../lib/types').XastElement} XastElement

*/
exports.fn = () => {
exports.fn = (root) => {
const stylesheet = collectStylesheet(root);
/**

@@ -30,5 +35,21 @@ * @type {Map<string, Array<XastElement>>}

/**
* Reference to the first defs element that is a direct child of the svg
* element if one exists.
*
* @type {XastElement}
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
*/
let svgDefs;
/**
* Set of hrefs that reference the id of another node.
*
* @type {Set<string>}
*/
const hrefs = new Set();
return {
element: {
enter: (node) => {
enter: (node, parentNode) => {
if (node.name === 'path' && node.attributes.d != null) {

@@ -46,2 +67,21 @@ const d = node.attributes.d;

}
if (
svgDefs == null &&
node.name === 'defs' &&
parentNode.type === 'element' &&
parentNode.name === 'svg'
) {
svgDefs = node;
}
if (node.name === 'use') {
for (const name of ['href', 'xlink:href']) {
const href = node.attributes[name];
if (href != null && href.startsWith('#') && href.length > 1) {
hrefs.add(href.slice(1));
}
}
}
},

@@ -51,37 +91,46 @@

if (node.name === 'svg' && parentNode.type === 'root') {
/**
* @type {XastElement}
*/
const defsTag = {
type: 'element',
name: 'defs',
attributes: {},
children: [],
};
// TODO remove legacy parentNode in v4
Object.defineProperty(defsTag, 'parentNode', {
writable: true,
value: node,
});
let defsTag = svgDefs;
if (defsTag == null) {
defsTag = {
type: 'element',
name: 'defs',
attributes: {},
children: [],
};
// TODO remove legacy parentNode in v4
Object.defineProperty(defsTag, 'parentNode', {
writable: true,
value: node,
});
}
let index = 0;
for (const list of paths.values()) {
if (list.length > 1) {
// add reusable path to defs
/**
* @type {XastElement}
*/
/** @type {XastElement} */
const reusablePath = {
type: 'element',
name: 'path',
attributes: { ...list[0].attributes },
attributes: {},
children: [],
};
delete reusablePath.attributes.transform;
let id;
if (reusablePath.attributes.id == null) {
id = 'reuse-' + index;
index += 1;
reusablePath.attributes.id = id;
for (const attr of ['fill', 'stroke', 'd']) {
if (list[0].attributes[attr] != null) {
reusablePath.attributes[attr] = list[0].attributes[attr];
}
}
const originalId = list[0].attributes.id;
if (
originalId == null ||
hrefs.has(originalId) ||
stylesheet.rules.some(
(rule) => rule.selector === `#${originalId}`
)
) {
reusablePath.attributes.id = 'reuse-' + index++;
} else {
id = reusablePath.attributes.id;
reusablePath.attributes.id = originalId;
delete list[0].attributes.id;

@@ -97,7 +146,39 @@ }

for (const pathNode of list) {
pathNode.name = 'use';
pathNode.attributes['xlink:href'] = '#' + id;
delete pathNode.attributes.d;
delete pathNode.attributes.stroke;
delete pathNode.attributes.fill;
if (
defsTag.children.includes(pathNode) &&
pathNode.children.length === 0
) {
if (Object.keys(pathNode.attributes).length === 0) {
detachNodeFromParent(pathNode, defsTag);
continue;
}
if (
Object.keys(pathNode.attributes).length === 1 &&
pathNode.attributes.id != null
) {
detachNodeFromParent(pathNode, defsTag);
const selector = `[xlink\\:href=#${pathNode.attributes.id}], [href=#${pathNode.attributes.id}]`;
for (const child of querySelectorAll(node, selector)) {
if (child.type !== 'element') {
continue;
}
for (const name of ['href', 'xlink:href']) {
if (child.attributes[name] != null) {
child.attributes[name] =
'#' + reusablePath.attributes.id;
}
}
}
continue;
}
}
pathNode.name = 'use';
pathNode.attributes['xlink:href'] =
'#' + reusablePath.attributes.id;
}

@@ -110,3 +191,6 @@ }

}
node.children.unshift(defsTag);
if (svgDefs == null) {
node.children.unshift(defsTag);
}
}

@@ -113,0 +197,0 @@ }

@@ -15,6 +15,8 @@ <div align="center">

Via npm:
```sh
# Via npm
npm -g install svgo
# Via yarn
```
Via yarn:
```sh
yarn global add svgo

@@ -25,8 +27,12 @@ ```

Processing single files:
```sh
# Processing single files:
svgo one.svg two.svg -o one.min.svg two.min.svg
# Processing directory of svg files, recursively using `-f`, `--folder` :
```
Processing directory of svg files, recursively using `-f`, `--folder`:
```sh
svgo -f ./path/to/folder/with/svg/files -o ./path/to/folder/with/svg/output
# Help for advanced usage
```
Help for advanced usage:
```sh
svgo --help

@@ -93,39 +99,4 @@ ```

Default preset includes the following list of plugins:
The default preset includes plugins marked with 'Yes' in the [plugin list](#built-in-plugins) below.
- removeDoctype
- removeXMLProcInst
- removeComments
- removeMetadata
- removeEditorsNSData
- cleanupAttrs
- mergeStyles
- inlineStyles
- minifyStyles
- cleanupIds
- removeUselessDefs
- cleanupNumericValues
- convertColors
- removeUnknownsAndDefaults
- removeNonInheritableGroupAttrs
- removeUselessStrokeAndFill
- removeViewBox
- cleanupEnableBackground
- removeHiddenElems
- removeEmptyText
- convertShapeToPath
- convertEllipseToCircle
- moveElemsAttrsToGroup
- moveGroupAttrsToElems
- collapseGroups
- convertPathData
- convertTransform
- removeEmptyAttrs
- removeEmptyContainers
- mergePaths
- removeUnusedNS
- sortDefsChildren
- removeTitle
- removeDesc
### Custom plugin

@@ -177,86 +148,99 @@

const config = await loadConfig();
```
// you can also specify a relative or absolute path and customize the current working directory
You can also specify a relative or absolute path and customize the current working directory.
```js
const config = await loadConfig(configFile, cwd);
```
## Troubleshooting
### SVG won't scale when CSS is applied on it.
**Observed Problem:** I'm using my SVG files on a website. It looks like the rendered SVG doesn't scale when the dimensions are altered using CSS.
**Possible Solution:** Try disabling `removeViewBox` in the configuration. See [issue #1128](https://github.com/svg/svgo/issues/1128) for details and discussion.
## Built-in plugins
| Plugin | Description | Default |
| ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| [cleanupAttrs](https://github.com/svg/svgo/blob/main/plugins/cleanupAttrs.js) | cleanup attributes from newlines, trailing, and repeating spaces | `enabled` |
| [mergeStyles](https://github.com/svg/svgo/blob/main/plugins/mergeStyles.js) | merge multiple style elements into one | `enabled` |
| [inlineStyles](https://github.com/svg/svgo/blob/main/plugins/inlineStyles.js) | move and merge styles from `<style>` elements to element `style` attributes | `enabled` |
| [removeDoctype](https://github.com/svg/svgo/blob/main/plugins/removeDoctype.js) | remove `doctype` declaration | `enabled` |
| [removeXMLProcInst](https://github.com/svg/svgo/blob/main/plugins/removeXMLProcInst.js) | remove XML processing instructions | `enabled` |
| [removeComments](https://github.com/svg/svgo/blob/main/plugins/removeComments.js) | remove comments | `enabled` |
| [removeMetadata](https://github.com/svg/svgo/blob/main/plugins/removeMetadata.js) | remove `<metadata>` | `enabled` |
| [removeTitle](https://github.com/svg/svgo/blob/main/plugins/removeTitle.js) | remove `<title>` | `enabled` |
| [removeDesc](https://github.com/svg/svgo/blob/main/plugins/removeDesc.js) | remove `<desc>` | `enabled` |
| [removeUselessDefs](https://github.com/svg/svgo/blob/main/plugins/removeUselessDefs.js) | remove elements of `<defs>` without `id` | `enabled` |
| [removeXMLNS](https://github.com/svg/svgo/blob/main/plugins/removeXMLNS.js) | removes the `xmlns` attribute (for inline SVG) | `disabled` |
| [removeEditorsNSData](https://github.com/svg/svgo/blob/main/plugins/removeEditorsNSData.js) | remove editors namespaces, elements, and attributes | `enabled` |
| [removeEmptyAttrs](https://github.com/svg/svgo/blob/main/plugins/removeEmptyAttrs.js) | remove empty attributes | `enabled` |
| [removeHiddenElems](https://github.com/svg/svgo/blob/main/plugins/removeHiddenElems.js) | remove hidden elements | `enabled` |
| [removeEmptyText](https://github.com/svg/svgo/blob/main/plugins/removeEmptyText.js) | remove empty Text elements | `enabled` |
| [removeEmptyContainers](https://github.com/svg/svgo/blob/main/plugins/removeEmptyContainers.js) | remove empty Container elements | `enabled` |
| [removeViewBox](https://github.com/svg/svgo/blob/main/plugins/removeViewBox.js) | remove `viewBox` attribute when possible | `enabled` |
| [cleanupEnableBackground](https://github.com/svg/svgo/blob/main/plugins/cleanupEnableBackground.js) | remove or cleanup `enable-background` attribute when possible | `enabled` |
| [minifyStyles](https://github.com/svg/svgo/blob/main/plugins/minifyStyles.js) | minify `<style>` elements content with [CSSO](https://github.com/css/csso) | `enabled` |
| [convertStyleToAttrs](https://github.com/svg/svgo/blob/main/plugins/convertStyleToAttrs.js) | convert styles into attributes | `disabled` |
| [convertColors](https://github.com/svg/svgo/blob/main/plugins/convertColors.js) | convert colors (from `rgb()` to `#rrggbb`, from `#rrggbb` to `#rgb`) | `enabled` |
| [convertPathData](https://github.com/svg/svgo/blob/main/plugins/convertPathData.js) | convert Path data to relative or absolute (whichever is shorter), convert one segment to another, trim useless delimiters, smart rounding, and much more | `enabled` |
| [convertTransform](https://github.com/svg/svgo/blob/main/plugins/convertTransform.js) | collapse multiple transforms into one, convert matrices to the short aliases, and much more | `enabled` |
| [removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/main/plugins/removeUnknownsAndDefaults.js) | remove unknown elements content and attributes, remove attributes with default values | `enabled` |
| [removeNonInheritableGroupAttrs](https://github.com/svg/svgo/blob/main/plugins/removeNonInheritableGroupAttrs.js) | remove non-inheritable group's "presentation" attributes | `enabled` |
| [removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/main/plugins/removeUselessStrokeAndFill.js) | remove useless `stroke` and `fill` attributes | `enabled` |
| [removeUnusedNS](https://github.com/svg/svgo/blob/main/plugins/removeUnusedNS.js) | remove unused namespaces declaration | `enabled` |
| [prefixIds](https://github.com/svg/svgo/blob/main/plugins/prefixIds.js) | prefix IDs and classes with the SVG filename or an arbitrary string | `disabled` |
| [cleanupIds](https://github.com/svg/svgo/blob/main/plugins/cleanupIds.js) | remove unused and minify used IDs | `enabled` |
| [cleanupNumericValues](https://github.com/svg/svgo/blob/main/plugins/cleanupNumericValues.js) | round numeric values to the fixed precision, remove default `px` units | `enabled` |
| [cleanupListOfValues](https://github.com/svg/svgo/blob/main/plugins/cleanupListOfValues.js) | round numeric values in attributes that take a list of numbers (like `viewBox` or `enable-background`) | `disabled` |
| [moveElemsAttrsToGroup](https://github.com/svg/svgo/blob/main/plugins/moveElemsAttrsToGroup.js) | move elements' attributes to their enclosing group | `enabled` |
| [moveGroupAttrsToElems](https://github.com/svg/svgo/blob/main/plugins/moveGroupAttrsToElems.js) | move some group attributes to the contained elements | `enabled` |
| [collapseGroups](https://github.com/svg/svgo/blob/main/plugins/collapseGroups.js) | collapse useless groups | `enabled` |
| [removeRasterImages](https://github.com/svg/svgo/blob/main/plugins/removeRasterImages.js) | remove raster images | `disabled` |
| [mergePaths](https://github.com/svg/svgo/blob/main/plugins/mergePaths.js) | merge multiple Paths into one | `enabled` |
| [convertShapeToPath](https://github.com/svg/svgo/blob/main/plugins/convertShapeToPath.js) | convert some basic shapes to `<path>` | `enabled` |
| [convertEllipseToCircle](https://github.com/svg/svgo/blob/main/plugins/convertEllipseToCircle.js) | convert non-eccentric `<ellipse>` to `<circle>` | `enabled` |
| [sortAttrs](https://github.com/svg/svgo/blob/main/plugins/sortAttrs.js) | sort element attributes for epic readability | `enabled` |
| [sortDefsChildren](https://github.com/svg/svgo/blob/main/plugins/sortDefsChildren.js) | sort children of `<defs>` in order to improve compression | `enabled` |
| [removeDimensions](https://github.com/svg/svgo/blob/main/plugins/removeDimensions.js) | remove `width`/`height` and add `viewBox` if it's missing (opposite to removeViewBox, disable it first) | `disabled` |
| [removeAttrs](https://github.com/svg/svgo/blob/main/plugins/removeAttrs.js) | remove attributes by pattern | `disabled` |
| [removeAttributesBySelector](https://github.com/svg/svgo/blob/main/plugins/removeAttributesBySelector.js) | removes attributes of elements that match a CSS selector | `disabled` |
| [removeElementsByAttr](https://github.com/svg/svgo/blob/main/plugins/removeElementsByAttr.js) | remove arbitrary elements by `ID` or `className` | `disabled` |
| [addClassesToSVGElement](https://github.com/svg/svgo/blob/main/plugins/addClassesToSVGElement.js) | add classnames to an outer `<svg>` element | `disabled` |
| [addAttributesToSVGElement](https://github.com/svg/svgo/blob/main/plugins/addAttributesToSVGElement.js) | adds attributes to an outer `<svg>` element | `disabled` |
| [removeOffCanvasPaths](https://github.com/svg/svgo/blob/main/plugins/removeOffCanvasPaths.js) | removes elements that are drawn outside of the viewbox | `disabled` |
| [removeStyleElement](https://github.com/svg/svgo/blob/main/plugins/removeStyleElement.js) | remove `<style>` elements | `disabled` |
| [removeScriptElement](https://github.com/svg/svgo/blob/main/plugins/removeScriptElement.js) | remove `<script>` elements | `disabled` |
| [reusePaths](https://github.com/svg/svgo/blob/main/plugins/reusePaths.js) | Find duplicated <path> elements and replace them with <use> links | `disabled` |
| Plugin | Description | Default |
| ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| [addAttributesToSVGElement](https://github.com/svg/svgo/blob/main/plugins/addAttributesToSVGElement.js) | adds attributes to an outer `<svg>` element | |
| [addClassesToSVGElement](https://github.com/svg/svgo/blob/main/plugins/addClassesToSVGElement.js) | add classnames to an outer `<svg>` element | |
| [cleanupAttrs](https://github.com/svg/svgo/blob/main/plugins/cleanupAttrs.js) | cleanup attributes from newlines, trailing, and repeating spaces | Yes |
| [cleanupEnableBackground](https://github.com/svg/svgo/blob/main/plugins/cleanupEnableBackground.js) | remove or cleanup `enable-background` attribute when possible | Yes |
| [cleanupIds](https://github.com/svg/svgo/blob/main/plugins/cleanupIds.js) | remove unused and minify used IDs | Yes |
| [cleanupListOfValues](https://github.com/svg/svgo/blob/main/plugins/cleanupListOfValues.js) | round numeric values in attributes that take a list of numbers (like `viewBox` or `enable-background`) | |
| [cleanupNumericValues](https://github.com/svg/svgo/blob/main/plugins/cleanupNumericValues.js) | round numeric values to the fixed precision, remove default `px` units | Yes |
| [collapseGroups](https://github.com/svg/svgo/blob/main/plugins/collapseGroups.js) | collapse useless groups | Yes |
| [convertColors](https://github.com/svg/svgo/blob/main/plugins/convertColors.js) | convert colors (from `rgb()` to `#rrggbb`, from `#rrggbb` to `#rgb`) | Yes |
| [convertEllipseToCircle](https://github.com/svg/svgo/blob/main/plugins/convertEllipseToCircle.js) | convert non-eccentric `<ellipse>` to `<circle>` | Yes |
| [convertOneStopGradients](https://github.com/svg/svgo/blob/main/plugins/convertOneStopGradients.js) | converts one-stop (single color) gradients to a plain color | |
| [convertPathData](https://github.com/svg/svgo/blob/main/plugins/convertPathData.js) | convert Path data to relative or absolute (whichever is shorter), convert one segment to another, trim useless delimiters, smart rounding, and much more | Yes |
| [convertShapeToPath](https://github.com/svg/svgo/blob/main/plugins/convertShapeToPath.js) | convert some basic shapes to `<path>` | Yes |
| [convertStyleToAttrs](https://github.com/svg/svgo/blob/main/plugins/convertStyleToAttrs.js) | convert styles into attributes | |
| [convertTransform](https://github.com/svg/svgo/blob/main/plugins/convertTransform.js) | collapse multiple transforms into one, convert matrices to the short aliases, and much more | Yes |
| [inlineStyles](https://github.com/svg/svgo/blob/main/plugins/inlineStyles.js) | move and merge styles from `<style>` elements to element `style` attributes | Yes |
| [mergePaths](https://github.com/svg/svgo/blob/main/plugins/mergePaths.js) | merge multiple Paths into one | Yes |
| [mergeStyles](https://github.com/svg/svgo/blob/main/plugins/mergeStyles.js) | merge multiple style elements into one | Yes |
| [minifyStyles](https://github.com/svg/svgo/blob/main/plugins/minifyStyles.js) | minify `<style>` elements content with [CSSO](https://github.com/css/csso) | Yes |
| [moveElemsAttrsToGroup](https://github.com/svg/svgo/blob/main/plugins/moveElemsAttrsToGroup.js) | move elements' attributes to their enclosing group | Yes |
| [moveGroupAttrsToElems](https://github.com/svg/svgo/blob/main/plugins/moveGroupAttrsToElems.js) | move some group attributes to the contained elements | Yes |
| [prefixIds](https://github.com/svg/svgo/blob/main/plugins/prefixIds.js) | prefix IDs and classes with the SVG filename or an arbitrary string | |
| [removeAttributesBySelector](https://github.com/svg/svgo/blob/main/plugins/removeAttributesBySelector.js) | removes attributes of elements that match a CSS selector | |
| [removeAttrs](https://github.com/svg/svgo/blob/main/plugins/removeAttrs.js) | remove attributes by pattern | |
| [removeComments](https://github.com/svg/svgo/blob/main/plugins/removeComments.js) | remove comments | Yes |
| [removeDesc](https://github.com/svg/svgo/blob/main/plugins/removeDesc.js) | remove `<desc>` | Yes |
| [removeDimensions](https://github.com/svg/svgo/blob/main/plugins/removeDimensions.js) | remove `width`/`height` and add `viewBox` if it's missing (opposite to removeViewBox, disable it first) | |
| [removeDoctype](https://github.com/svg/svgo/blob/main/plugins/removeDoctype.js) | remove `doctype` declaration | Yes |
| [removeEditorsNSData](https://github.com/svg/svgo/blob/main/plugins/removeEditorsNSData.js) | remove editors namespaces, elements, and attributes | Yes |
| [removeElementsByAttr](https://github.com/svg/svgo/blob/main/plugins/removeElementsByAttr.js) | remove arbitrary elements by `ID` or `className` | |
| [removeEmptyAttrs](https://github.com/svg/svgo/blob/main/plugins/removeEmptyAttrs.js) | remove empty attributes | Yes |
| [removeEmptyContainers](https://github.com/svg/svgo/blob/main/plugins/removeEmptyContainers.js) | remove empty Container elements | Yes |
| [removeEmptyText](https://github.com/svg/svgo/blob/main/plugins/removeEmptyText.js) | remove empty Text elements | Yes |
| [removeHiddenElems](https://github.com/svg/svgo/blob/main/plugins/removeHiddenElems.js) | remove hidden elements | Yes |
| [removeMetadata](https://github.com/svg/svgo/blob/main/plugins/removeMetadata.js) | remove `<metadata>` | Yes |
| [removeNonInheritableGroupAttrs](https://github.com/svg/svgo/blob/main/plugins/removeNonInheritableGroupAttrs.js) | remove non-inheritable group's "presentation" attributes | Yes |
| [removeOffCanvasPaths](https://github.com/svg/svgo/blob/main/plugins/removeOffCanvasPaths.js) | removes elements that are drawn outside of the viewbox | |
| [removeRasterImages](https://github.com/svg/svgo/blob/main/plugins/removeRasterImages.js) | remove raster images | |
| [removeScriptElement](https://github.com/svg/svgo/blob/main/plugins/removeScriptElement.js) | remove scripts | |
| [removeStyleElement](https://github.com/svg/svgo/blob/main/plugins/removeStyleElement.js) | remove `<style>` elements | |
| [removeTitle](https://github.com/svg/svgo/blob/main/plugins/removeTitle.js) | remove `<title>` | Yes |
| [removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/main/plugins/removeUnknownsAndDefaults.js) | remove unknown elements content and attributes, remove attributes with default values | Yes |
| [removeUnusedNS](https://github.com/svg/svgo/blob/main/plugins/removeUnusedNS.js) | remove unused namespaces declaration | Yes |
| [removeUselessDefs](https://github.com/svg/svgo/blob/main/plugins/removeUselessDefs.js) | remove elements of `<defs>` without `id` | Yes |
| [removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/main/plugins/removeUselessStrokeAndFill.js) | remove useless `stroke` and `fill` attributes | Yes |
| [removeViewBox](https://github.com/svg/svgo/blob/main/plugins/removeViewBox.js) | remove `viewBox` attribute when possible | Yes |
| [removeXMLNS](https://github.com/svg/svgo/blob/main/plugins/removeXMLNS.js) | removes the `xmlns` attribute (for inline SVG) | |
| [removeXMLProcInst](https://github.com/svg/svgo/blob/main/plugins/removeXMLProcInst.js) | remove XML processing instructions | Yes |
| [reusePaths](https://github.com/svg/svgo/blob/main/plugins/reusePaths.js) | Find duplicated <path> elements and replace them with <use> links | |
| [sortAttrs](https://github.com/svg/svgo/blob/main/plugins/sortAttrs.js) | sort element attributes for epic readability | Yes |
| [sortDefsChildren](https://github.com/svg/svgo/blob/main/plugins/sortDefsChildren.js) | sort children of `<defs>` in order to improve compression | Yes |
## Other Ways to Use SVGO
## Other ways to use SVGO
- as a web app – [SVGOMG](https://jakearchibald.github.io/svgomg/)
- as a GitHub Action – [SVGO Action](https://github.com/marketplace/actions/svgo-action)
- as a Grunt task – [grunt-svgmin](https://github.com/sindresorhus/grunt-svgmin)
- as a Gulp task – [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin)
- as a Mimosa module – [mimosa-minify-svg](https://github.com/dbashford/mimosa-minify-svg)
- as an OSX Folder Action – [svgo-osx-folder-action](https://github.com/svg/svgo-osx-folder-action)
- as a webpack loader – [image-webpack-loader](https://github.com/tcoopman/image-webpack-loader)
- as a Telegram Bot – [svgo_bot](https://github.com/maksugr/svgo_bot)
- as a PostCSS plugin – [postcss-svgo](https://github.com/ben-eb/postcss-svgo)
- as an Inkscape plugin – [inkscape-svgo](https://github.com/konsumer/inkscape-svgo)
- as a Sketch plugin - [svgo-compressor](https://github.com/BohemianCoding/svgo-compressor)
- as a macOS app - [Image Shrinker](https://image-shrinker.com)
- as a Rollup plugin - [rollup-plugin-svgo](https://github.com/porsager/rollup-plugin-svgo)
- as a VS Code plugin - [vscode-svgo](https://github.com/1000ch/vscode-svgo)
- as a Atom plugin - [atom-svgo](https://github.com/1000ch/atom-svgo)
- as a Sublime plugin - [Sublime-svgo](https://github.com/1000ch/Sublime-svgo)
- as a Figma plugin - [Advanced SVG Export](https://www.figma.com/c/plugin/782713260363070260/Advanced-SVG-Export)
- as a Linux app - [Oh My SVG](https://github.com/sonnyp/OhMySVG)
- as a Browser extension - [SVG Gobbler](https://github.com/rossmoody/svg-gobbler)
- as an API - [Vector Express](https://github.com/smidyo/vectorexpress-api#convertor-svgo)
| Method | Reference |
| ------ | --------- |
| Web app | [SVGOMG](https://jakearchibald.github.io/svgomg/) |
| GitHub Action | [SVGO Action](https://github.com/marketplace/actions/svgo-action) |
| Grunt task | [grunt-svgmin](https://github.com/sindresorhus/grunt-svgmin) |
| Gulp task | [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin) |
| Mimosa module | [mimosa-minify-svg](https://github.com/dbashford/mimosa-minify-svg) |
| OSX Folder Action | [svgo-osx-folder-action](https://github.com/svg/svgo-osx-folder-action) |
| Webpack loader | [image-minimizer-webpack-plugin](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/#optimize-with-svgo) |
| Telegram Bot | [svgo_bot](https://github.com/maksugr/svgo_bot) |
| PostCSS plugin | [postcss-svgo](https://github.com/cssnano/cssnano/tree/master/packages/postcss-svgo) |
| Inkscape plugin | [inkscape-svgo](https://github.com/konsumer/inkscape-svgo) |
| Sketch plugin | [svgo-compressor](https://github.com/BohemianCoding/svgo-compressor) |
| macOS app | [Image Shrinker](https://image-shrinker.com) |
| Rollup plugin | [rollup-plugin-svgo](https://github.com/porsager/rollup-plugin-svgo) |
| VS Code plugin | [vscode-svgo](https://github.com/1000ch/vscode-svgo) |
| Atom plugin | [atom-svgo](https://github.com/1000ch/atom-svgo) |
| Sublime plugin | [Sublime-svgo](https://github.com/1000ch/Sublime-svgo) |
| Figma plugin | [Advanced SVG Export](https://www.figma.com/c/plugin/782713260363070260/Advanced-SVG-Export) |
| Linux app | [Oh My SVG](https://github.com/sonnyp/OhMySVG) |
| Browser extension | [SVG Gobbler](https://github.com/rossmoody/svg-gobbler) |
| API | [Vector Express](https://github.com/smidyo/vectorexpress-api#convertor-svgo) |
## Donators
## Donors

@@ -263,0 +247,0 @@ | [<img src="https://sheetjs.com/sketch128.png" width="80">](https://sheetjs.com/) | [<img src="https://raw.githubusercontent.com/fontello/fontello/8.0.0/fontello-image.svg" width="80">](https://fontello.com/) |

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc