Socket
Socket
Sign inDemoInstall

svgo

Package Overview
Dependencies
16
Maintainers
3
Versions
99
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.8.0 to 3.0.0

lib/builtin.js

17

lib/parser.js

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

* @typedef {import('./types').XastParent} XastParent
* @typedef {import('./types').XastChild} XastChild
*/

@@ -18,3 +19,2 @@

const SAX = require('@trysound/sax');
const JSAPI = require('./svgo/jsAPI.js');
const { textElems } = require('../plugins/_collections.js');

@@ -99,3 +99,3 @@

*/
const root = new JSAPI({ type: 'root', children: [] });
const root = { type: 'root', children: [] };
/**

@@ -111,8 +111,11 @@ * @type {XastParent}

/**
* @type {<T extends XastNode>(node: T) => T}
* @type {(node: XastChild) => void}
*/
const pushToContent = (node) => {
const wrapped = new JSAPI(node, current);
current.children.push(wrapped);
return wrapped;
// TODO remove legacy parentNode in v4
Object.defineProperty(node, 'parentNode', {
writable: true,
value: current,
});
current.children.push(node);
};

@@ -206,3 +209,3 @@

}
element = pushToContent(element);
pushToContent(element);
current = element;

@@ -209,0 +212,0 @@ stack.push(element);

@@ -19,4 +19,2 @@ 'use strict';

* @typedef {{
* width: void | string,
* height: void | string,
* indent: string,

@@ -85,9 +83,3 @@ * textContext: null | XastElement,

*
* @type {(data: XastRoot, config: StringifyOptions) => {
* data: string,
* info: {
* width: void | string,
* height: void | string
* }
* }}
* @type {(data: XastRoot, config: StringifyOptions) => string}
*/

@@ -110,5 +102,2 @@ const stringifySvg = (data, userOptions = {}) => {

const state = {
// TODO remove width and height in v3
width: undefined,
height: undefined,
indent: newIndent,

@@ -133,9 +122,3 @@ textContext: null,

}
return {
data: svg,
info: {
width: state.width,
height: state.height,
},
};
return svg;
};

@@ -226,12 +209,2 @@ exports.stringifySvg = stringifySvg;

const stringifyElement = (node, config, state) => {
// beautiful injection for obtaining SVG information :)
if (
node.name === 'svg' &&
node.attributes.width != null &&
node.attributes.height != null
) {
state.width = node.attributes.width;
state.height = node.attributes.height;
}
// empty element and short tag

@@ -238,0 +211,0 @@ if (node.children.length === 0) {

@@ -16,6 +16,7 @@ 'use strict';

const stable = require('stable');
const csstree = require('css-tree');
// @ts-ignore not defined in @types/csso
const specificity = require('csso/lib/restructure/prepare/specificity');
const {
// @ts-ignore not defined in @types/csso
syntax: { specificity },
} = require('csso');
const { visit, matches } = require('./xast.js');

@@ -32,7 +33,5 @@ const {

/**
* @type {(ruleNode: CsstreeRule, dynamic: boolean) => StylesheetRule}
* @type {(ruleNode: CsstreeRule, dynamic: boolean) => StylesheetRule[]}
*/
const parseRule = (ruleNode, dynamic) => {
let selectors;
let selectorsSpecificity;
/**

@@ -42,16 +41,4 @@ * @type {Array<StylesheetDeclaration>}

const declarations = [];
csstree.walk(ruleNode, (cssNode) => {
if (cssNode.type === 'SelectorList') {
// compute specificity from original node to consider pseudo classes
selectorsSpecificity = specificity(cssNode);
const newSelectorsNode = csstree.clone(cssNode);
csstree.walk(newSelectorsNode, (pseudoClassNode, item, list) => {
if (pseudoClassNode.type === 'PseudoClassSelector') {
dynamic = true;
list.remove(item);
}
});
selectors = csstree.generate(newSelectorsNode);
return csstreeWalkSkip;
}
// collect declarations
ruleNode.block.children.forEach((cssNode) => {
if (cssNode.type === 'Declaration') {

@@ -63,14 +50,30 @@ declarations.push({

});
return csstreeWalkSkip;
}
});
if (selectors == null || selectorsSpecificity == null) {
throw Error('assert');
}
return {
dynamic,
selectors,
specificity: selectorsSpecificity,
declarations,
};
/**
* @type {StylesheetRule[]}
*/
const rules = [];
csstree.walk(ruleNode.prelude, (node) => {
if (node.type === 'Selector') {
const newNode = csstree.clone(node);
let hasPseudoClasses = false;
csstree.walk(newNode, (pseudoClassNode, item, list) => {
if (pseudoClassNode.type === 'PseudoClassSelector') {
hasPseudoClasses = true;
list.remove(item);
}
});
rules.push({
specificity: specificity(node),
dynamic: hasPseudoClasses || dynamic,
// compute specificity from original node to consider pseudo classes
selector: csstree.generate(newNode),
declarations,
});
}
});
return rules;
};

@@ -92,3 +95,3 @@

if (cssNode.type === 'Rule') {
rules.push(parseRule(cssNode, dynamic || false));
rules.push(...parseRule(cssNode, dynamic || false));
return csstreeWalkSkip;

@@ -102,3 +105,3 @@ }

if (ruleNode.type === 'Rule') {
rules.push(parseRule(ruleNode, dynamic || true));
rules.push(...parseRule(ruleNode, dynamic || true));
return csstreeWalkSkip;

@@ -156,4 +159,4 @@ }

// collect matching rules
for (const { selectors, declarations, dynamic } of stylesheet.rules) {
if (matches(node, selectors)) {
for (const { selector, declarations, dynamic } of stylesheet.rules) {
if (matches(node, selector)) {
for (const { name, value, important } of declarations) {

@@ -205,3 +208,3 @@ const computed = computedStyle[name];

* Compares two selector specificities.
* extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
* extracted from https://github.com/keeganstreet/specificity/blob/main/specificity.js#L211
*

@@ -211,3 +214,3 @@ * @type {(a: Specificity, b: Specificity) => number}

const compareSpecificity = (a, b) => {
for (var i = 0; i < 4; i += 1) {
for (let i = 0; i < 4; i += 1) {
if (a[i] < b[i]) {

@@ -261,5 +264,3 @@ return -1;

// sort by selectors specificity
stable.inplace(rules, (a, b) =>
compareSpecificity(a.specificity, b.specificity)
);
rules.sort((a, b) => compareSpecificity(a.specificity, b.specificity));
return { rules, parents };

@@ -266,0 +267,0 @@ };

@@ -7,11 +7,4 @@ 'use strict';

const path = require('path');
const {
extendDefaultPlugins,
optimize: optimizeAgnostic,
createContentItem,
} = require('./svgo.js');
const { optimize: optimizeAgnostic } = require('./svgo.js');
exports.extendDefaultPlugins = extendDefaultPlugins;
exports.createContentItem = createContentItem;
const importConfig = async (configFile) => {

@@ -25,20 +18,6 @@ let config;

} else {
try {
// dynamic import expects file url instead of path and may fail
// when windows path is provided
const { default: imported } = await import(pathToFileURL(configFile));
config = imported;
} catch (importError) {
// TODO remove require in v3
try {
config = require(configFile);
} catch (requireError) {
// throw original error if es module is detected
if (requireError.code === 'ERR_REQUIRE_ESM') {
throw importError;
} else {
throw requireError;
}
}
}
// dynamic import expects file url instead of path and may fail
// when windows path is provided
const { default: imported } = await import(pathToFileURL(configFile));
config = imported;
}

@@ -45,0 +24,0 @@ if (config == null || typeof config !== 'object' || Array.isArray(config)) {

'use strict';
const {
defaultPlugins,
resolvePluginConfig,
extendDefaultPlugins,
} = require('./svgo/config.js');
const { parseSvg } = require('./parser.js');
const { stringifySvg } = require('./stringifier.js');
const { builtin } = require('./builtin.js');
const { invokePlugins } = require('./svgo/plugins.js');
const JSAPI = require('./svgo/jsAPI.js');
const { encodeSVGDatauri } = require('./svgo/tools.js');
exports.extendDefaultPlugins = extendDefaultPlugins;
const pluginsMap = {};
for (const plugin of builtin) {
pluginsMap[plugin.name] = plugin;
}
const resolvePluginConfig = (plugin) => {
if (typeof plugin === 'string') {
// resolve builtin plugin specified as string
const builtinPlugin = pluginsMap[plugin];
if (builtinPlugin == null) {
throw Error(`Unknown builtin plugin "${plugin}" specified.`);
}
return {
name: plugin,
params: {},
fn: builtinPlugin.fn,
};
}
if (typeof plugin === 'object' && plugin != null) {
if (plugin.name == null) {
throw Error(`Plugin name should be specified`);
}
// use custom plugin implementation
let fn = plugin.fn;
if (fn == null) {
// resolve builtin plugin implementation
const builtinPlugin = pluginsMap[plugin.name];
if (builtinPlugin == null) {
throw Error(`Unknown builtin plugin "${plugin.name}" specified.`);
}
fn = builtinPlugin.fn;
}
return {
name: plugin.name,
params: plugin.params,
fn,
};
}
return null;
};
const optimize = (input, config) => {

@@ -25,3 +59,3 @@ if (config == null) {

let prevResultSize = Number.POSITIVE_INFINITY;
let svgjs = null;
let output = '';
const info = {};

@@ -33,15 +67,4 @@ if (config.path != null) {

info.multipassCount = i;
// TODO throw this error in v3
try {
svgjs = parseSvg(input, config.path);
} catch (error) {
return { error: error.toString(), modernError: error };
}
if (svgjs.error != null) {
if (config.path != null) {
svgjs.path = config.path;
}
return svgjs;
}
const plugins = config.plugins || defaultPlugins;
const ast = parseSvg(input, config.path);
const plugins = config.plugins || ['preset-default'];
if (Array.isArray(plugins) === false) {

@@ -57,30 +80,18 @@ throw Error(

}
svgjs = invokePlugins(svgjs, info, resolvedPlugins, null, globalOverrides);
svgjs = stringifySvg(svgjs, config.js2svg);
if (svgjs.data.length < prevResultSize) {
input = svgjs.data;
prevResultSize = svgjs.data.length;
invokePlugins(ast, info, resolvedPlugins, null, globalOverrides);
output = stringifySvg(ast, config.js2svg);
if (output.length < prevResultSize) {
input = output;
prevResultSize = output.length;
} else {
if (config.datauri) {
svgjs.data = encodeSVGDatauri(svgjs.data, config.datauri);
}
if (config.path != null) {
svgjs.path = config.path;
}
return svgjs;
break;
}
}
return svgjs;
if (config.datauri) {
output = encodeSVGDatauri(output, config.datauri);
}
return {
data: output,
};
};
exports.optimize = optimize;
/**
* The factory that creates a content item with the helper methods.
*
* @param {Object} data which is passed to jsAPI constructor
* @returns {JSAPI} content item
*/
const createContentItem = (data) => {
return new JSAPI(data);
};
exports.createContentItem = createContentItem;

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

const { loadConfig, optimize } = require('../svgo-node.js');
const pluginsMap = require('../../plugins/plugins.js');
const { builtin } = require('../builtin.js');
const PKG = require('../../package.json');

@@ -389,6 +389,12 @@ const { encodeSVGDatauri, decodeSVGDatauri } = require('./tools.js');

const result = optimize(data, { ...config, ...info });
if (result.modernError) {
console.error(colors.red(result.modernError.toString()));
process.exit(1);
let result;
try {
result = optimize(data, { ...config, ...info });
} catch (error) {
if (error.name === 'SvgoParserError') {
console.error(colors.red(error.toString()));
process.exit(1);
} else {
throw error;
}
}

@@ -431,3 +437,3 @@ if (config.datauri) {

if (output == '-') {
console.log(data);
process.stdout.write(data);
return Promise.resolve();

@@ -513,5 +519,5 @@ }

function showAvailablePlugins() {
const list = Object.entries(pluginsMap)
.sort(([a], [b]) => a.localeCompare(b))
.map(([name, plugin]) => ` [ ${colors.green(name)} ] ${plugin.description}`)
const list = builtin
.sort((a, b) => a.name.localeCompare(b.name))
.map((plugin) => ` [ ${colors.green(plugin.name)} ] ${plugin.description}`)
.join('\n');

@@ -518,0 +524,0 @@ console.log('Currently available plugins:\n' + list);

@@ -23,62 +23,13 @@ 'use strict';

if (plugin.type === 'perItem') {
ast = perItem(ast, info, plugin, params);
const visitor = plugin.fn(ast, params, info);
if (visitor != null) {
visit(ast, visitor);
}
if (plugin.type === 'perItemReverse') {
ast = perItem(ast, info, plugin, params, true);
}
if (plugin.type === 'full') {
if (plugin.active) {
ast = plugin.fn(ast, params, info);
}
}
if (plugin.type === 'visitor') {
if (plugin.active) {
const visitor = plugin.fn(ast, params, info);
if (visitor != null) {
visit(ast, visitor);
}
}
}
}
return ast;
};
exports.invokePlugins = invokePlugins;
/**
* Direct or reverse per-item loop.
*
* @param {Object} data input data
* @param {Object} info extra information
* @param {Array} plugins plugins list to process
* @param {boolean} [reverse] reverse pass?
* @return {Object} output data
*/
function perItem(data, info, plugin, params, reverse) {
function monkeys(items) {
items.children = items.children.filter(function (item) {
// reverse pass
if (reverse && item.children) {
monkeys(item);
}
// main filter
let kept = true;
if (plugin.active) {
kept = plugin.fn(item, params, info) !== false;
}
// direct pass
if (!reverse && item.children) {
monkeys(item);
}
return kept;
});
return items;
}
return monkeys(data);
}
const createPreset = ({ name, plugins }) => {
return {
name,
type: 'full',
fn: (ast, params, info) => {

@@ -91,12 +42,13 @@ const { floatPrecision, overrides } = params;

if (overrides) {
for (const [pluginName, override] of Object.entries(overrides)) {
if (override === true) {
const pluginNames = plugins.map(({ name }) => name);
for (const pluginName of Object.keys(overrides)) {
if (!pluginNames.includes(pluginName)) {
console.warn(
`You are trying to enable ${pluginName} which is not part of preset.\n` +
`Try to put it before or after preset, for example\n\n` +
`You are trying to configure ${pluginName} which is not part of ${name}.\n` +
`Try to put it before or after, for example\n\n` +
`plugins: [\n` +
` {\n` +
` name: 'preset-default',\n` +
` name: '${name}',\n` +
` },\n` +
` 'cleanupListOfValues'\n` +
` '${pluginName}'\n` +
`]\n`

@@ -107,3 +59,3 @@ );

}
return invokePlugins(ast, info, plugins, overrides, globalOverrides);
invokePlugins(ast, info, plugins, overrides, globalOverrides);
},

@@ -110,0 +62,0 @@ };

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

* @typedef {import('../types').PathDataCommand} PathDataCommand
* @typedef {import('../types').DataUri} DataUri
*/

@@ -11,3 +12,3 @@

*
* @type {(str: string, type?: 'base64' | 'enc' | 'unenc') => string}
* @type {(str: string, type?: DataUri) => string}
*/

@@ -14,0 +15,0 @@ exports.encodeSVGDatauri = (str, type) => {

@@ -124,3 +124,3 @@ export type XastDoctype = {

dynamic: boolean;
selectors: string;
selector: string;
specificity: Specificity;

@@ -174,1 +174,3 @@ declarations: Array<StylesheetDeclaration>;

};
export type DataUri = 'base64' | 'enc' | 'unenc';

@@ -42,18 +42,2 @@ 'use strict';

/**
* @type {(node: XastChild, name: string) => null | XastChild}
*/
const closestByName = (node, name) => {
let currentNode = node;
while (currentNode) {
if (currentNode.type === 'element' && currentNode.name === name) {
return currentNode;
}
// @ts-ignore parentNode is hidden from public usage
currentNode = currentNode.parentNode;
}
return null;
};
exports.closestByName = closestByName;
const visitSkip = Symbol();

@@ -60,0 +44,0 @@ exports.visitSkip = visitSkip;

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

@@ -43,4 +43,9 @@ "license": "MIT",

},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/svgo"
},
"main": "./lib/svgo-node.js",
"bin": "./bin/svgo",
"types": "./lib/svgo.d.ts",
"files": [

@@ -54,3 +59,3 @@ "bin",

"engines": {
"node": ">=10.13.0"
"node": ">=14.0.0"
},

@@ -107,18 +112,17 @@ "scripts": {

"commander": "^7.2.0",
"css-select": "^4.1.3",
"css-tree": "^1.1.3",
"csso": "^4.2.0",
"picocolors": "^1.0.0",
"stable": "^0.1.8"
"css-select": "^5.1.0",
"css-tree": "^2.2.1",
"csso": "^5.0.5",
"picocolors": "^1.0.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-commonjs": "^22.0.2",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.0.4",
"@types/css-tree": "^1.0.6",
"@types/csso": "^4.2.0",
"@types/jest": "^27.0.1",
"@rollup/plugin-node-resolve": "^14.1.0",
"@types/css-tree": "^1.0.7",
"@types/csso": "^5.0.0",
"@types/jest": "^29.1.1",
"del": "^6.0.0",
"eslint": "^7.32.0",
"jest": "^27.2.5",
"eslint": "^8.24.0",
"jest": "^29.1.2",
"node-fetch": "^2.6.2",

@@ -128,8 +132,8 @@ "pixelmatch": "^5.2.1",

"pngjs": "^6.0.0",
"prettier": "^2.4.0",
"rollup": "^2.56.3",
"prettier": "^2.7.1",
"rollup": "^2.79.1",
"rollup-plugin-terser": "^7.0.2",
"tar-stream": "^2.2.0",
"typescript": "^4.4.3"
"typescript": "^4.8.4"
}
}
}
'use strict';
exports.name = 'addAttributesToSVGElement';
exports.type = 'visitor';
exports.active = false;
exports.description = 'adds attributes to an outer <svg> element';

@@ -7,0 +5,0 @@

'use strict';
exports.name = 'addClassesToSVGElement';
exports.type = 'visitor';
exports.active = false;
exports.description = 'adds classnames to an outer <svg> element';

@@ -7,0 +5,0 @@

'use strict';
exports.name = 'cleanupAttrs';
exports.type = 'visitor';
exports.active = true;
exports.description =

@@ -7,0 +5,0 @@ 'cleanups attributes from newlines, trailing and repeating spaces';

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

exports.type = 'visitor';
exports.name = 'cleanupEnableBackground';
exports.active = true;
exports.description =

@@ -10,0 +8,0 @@ 'remove or cleanup enable-background attribute when possible';

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'cleanupListOfValues';
exports.type = 'visitor';
exports.active = false;
exports.description = 'rounds list of values to the fixed precision';

@@ -10,0 +8,0 @@

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'cleanupNumericValues';
exports.type = 'visitor';
exports.active = true;
exports.description =

@@ -10,0 +8,0 @@ 'rounds numeric values to the fixed precision, removes default ‘px’ units';

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

exports.type = 'visitor';
exports.name = 'collapseGroups';
exports.active = true;
exports.description = 'collapses useless groups';

@@ -126,7 +124,8 @@

parentNode.children.splice(index, 1, ...node.children);
// TODO remove in v3
// TODO remove legacy parentNode in v4
for (const child of node.children) {
// @ts-ignore parentNode is forbidden for public usage
// and will be moved in v3
child.parentNode = parentNode;
Object.defineProperty(child, 'parentNode', {
writable: true,
value: parentNode,
});
}

@@ -133,0 +132,0 @@ }

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

exports.type = 'visitor';
exports.name = 'convertColors';
exports.active = true;
exports.description = 'converts colors: rgb() to #rrggbb and #rrggbb to #rgb';

@@ -10,0 +8,0 @@

'use strict';
exports.name = 'convertEllipseToCircle';
exports.type = 'visitor';
exports.active = true;
exports.description = 'converts non-eccentric <ellipse>s to <circle>s';

@@ -7,0 +5,0 @@

'use strict';
/**
* @typedef {import('../lib//types').PathDataItem} PathDataItem
*/
const { collectStylesheet, computeStyle } = require('../lib/style.js');
const { visit } = require('../lib/xast.js');
const { pathElems } = require('./_collections.js');
const { path2js, js2path } = require('./_path.js');
const { applyTransforms } = require('./_applyTransforms.js');
const { applyTransforms } = require('./applyTransforms.js');
const { cleanupOutData } = require('../lib/svgo/tools');
exports.name = 'convertPathData';
exports.type = 'visitor';
exports.active = true;
exports.description =
'optimizes path data: writes in shorter form, applies transformations';
exports.params = {
applyTransforms: true,
applyTransformsStroked: true,
makeArcs: {
threshold: 2.5, // coefficient of rounding error
tolerance: 0.5, // percentage of radius
},
straightCurves: true,
lineShorthands: true,
curveSmoothShorthands: true,
floatPrecision: 3,
transformPrecision: 5,
removeUseless: true,
collapseRepeated: true,
utilizeAbsolute: true,
leadingZero: true,
negativeExtraSpace: true,
noSpaceAfterFlags: false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
forceAbsolutePath: false,
};
/**
* @type {(data: number[]) => number[]}
*/
let roundData;
/**
* @type {number | false}
*/
let precision;
/**
* @type {number}
*/
let error;
/**
* @type {number}
*/
let arcThreshold;
/**
* @type {number}
*/
let arcTolerance;
/**
* @typedef {{
* applyTransforms: boolean,
* applyTransformsStroked: boolean,
* makeArcs: {
* threshold: number,
* tolerance: number,
* },
* straightCurves: boolean,
* lineShorthands: boolean,
* curveSmoothShorthands: boolean,
* floatPrecision: number | false,
* transformPrecision: number,
* removeUseless: boolean,
* collapseRepeated: boolean,
* utilizeAbsolute: boolean,
* leadingZero: boolean,
* negativeExtraSpace: boolean,
* noSpaceAfterFlags: boolean,
* forceAbsolutePath: boolean,
* }} InternalParams
*/
/**
* @typedef {[number, number]} Point
*/
/**
* @typedef {{
* center: Point,
* radius: number
* }} Circle
*/
/**
* Convert absolute Path to relative,

@@ -52,9 +83,81 @@ * collapse repeated instructions,

*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
* @author Kir Belevich
*
* @author Kir Belevich
* @type {import('../lib/types').Plugin<{
* applyTransforms?: boolean,
* applyTransformsStroked?: boolean,
* makeArcs?: {
* threshold: number,
* tolerance: number,
* },
* straightCurves?: boolean,
* lineShorthands?: boolean,
* curveSmoothShorthands?: boolean,
* floatPrecision?: number | false,
* transformPrecision?: number,
* removeUseless?: boolean,
* collapseRepeated?: boolean,
* utilizeAbsolute?: boolean,
* leadingZero?: boolean,
* negativeExtraSpace?: boolean,
* noSpaceAfterFlags?: boolean,
* forceAbsolutePath?: boolean,
* }>}
*/
exports.fn = (root, params) => {
const {
// TODO convert to separate plugin in v3
applyTransforms: _applyTransforms = true,
applyTransformsStroked = true,
makeArcs = {
threshold: 2.5, // coefficient of rounding error
tolerance: 0.5, // percentage of radius
},
straightCurves = true,
lineShorthands = true,
curveSmoothShorthands = true,
floatPrecision = 3,
transformPrecision = 5,
removeUseless = true,
collapseRepeated = true,
utilizeAbsolute = true,
leadingZero = true,
negativeExtraSpace = true,
noSpaceAfterFlags = false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
forceAbsolutePath = false,
} = params;
/**
* @type {InternalParams}
*/
const newParams = {
applyTransforms: _applyTransforms,
applyTransformsStroked,
makeArcs,
straightCurves,
lineShorthands,
curveSmoothShorthands,
floatPrecision,
transformPrecision,
removeUseless,
collapseRepeated,
utilizeAbsolute,
leadingZero,
negativeExtraSpace,
noSpaceAfterFlags,
forceAbsolutePath,
};
// invoke applyTransforms plugin
if (_applyTransforms) {
visit(
root,
// @ts-ignore
applyTransforms(root, {
transformPrecision,
applyTransformsStroked,
})
);
}
const stylesheet = collectStylesheet(root);

@@ -66,3 +169,3 @@ return {

const computedStyle = computeStyle(stylesheet, node);
precision = params.floatPrecision;
precision = floatPrecision;
error =

@@ -73,5 +176,5 @@ precision !== false

roundData = precision > 0 && precision < 20 ? strongRound : round;
if (params.makeArcs) {
arcThreshold = params.makeArcs.threshold;
arcTolerance = params.makeArcs.tolerance;
if (makeArcs) {
arcThreshold = makeArcs.threshold;
arcTolerance = makeArcs.tolerance;
}

@@ -94,9 +197,5 @@ const hasMarkerMid = computedStyle['marker-mid'] != null;

if (data.length) {
if (params.applyTransforms) {
applyTransforms(node, data, params);
}
convertToRelative(data);
data = filters(data, params, {
data = filters(data, newParams, {
maybeHasStrokeAndLinecap,

@@ -106,7 +205,8 @@ hasMarkerMid,

if (params.utilizeAbsolute) {
data = convertToMixed(data, params);
if (utilizeAbsolute) {
data = convertToMixed(data, newParams);
}
js2path(node, data, params);
// @ts-ignore
js2path(node, data, newParams);
}

@@ -122,5 +222,3 @@ }

*
* @param {Array} path input path data
* @param {Object} params plugin params
* @return {Array} output path data
* @type {(pathData: PathDataItem[]) => PathDataItem[]}
*/

@@ -284,4 +382,7 @@ const convertToRelative = (pathData) => {

// base should preserve reference from other element
// @ts-ignore
pathItem.base = prevCoords;
// @ts-ignore
pathItem.coords = [cursor[0], cursor[1]];
// @ts-ignore
prevCoords = pathItem.coords;

@@ -296,5 +397,7 @@ }

*
* @param {Array} path input path data
* @param {Object} params plugin params
* @return {Array} output path data
* @type {(
* path: PathDataItem[],
* params: InternalParams,
* aux: { maybeHasStrokeAndLinecap: boolean, hasMarkerMid: boolean }
* ) => PathDataItem[]}
*/

@@ -319,10 +422,9 @@ function filters(path, params, { maybeHasStrokeAndLinecap, hasMarkerMid }) {

if (command === 'c' || command === 's') {
var pdata = prev.args,
n = pdata.length;
// @ts-ignore
var pdata = prev.args,
n = pdata.length;
// (-x, -y) of the prev tangent point relative to the current point
sdata[0] = pdata[n - 2] - pdata[n - 4];
sdata[1] = pdata[n - 1] - pdata[n - 3];
}
// (-x, -y) of the prev tangent point relative to the current point
sdata[0] = pdata[n - 2] - pdata[n - 4];
sdata[1] = pdata[n - 1] - pdata[n - 3];
}

@@ -340,10 +442,21 @@

sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0,
/**
* @type {PathDataItem}
*/
arc = {
command: 'a',
args: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
// @ts-ignore
coords: item.coords.slice(),
// @ts-ignore
base: item.base,
},
/**
* @type {PathDataItem[]}
*/
output = [arc],
// relative coordinates to adjust the found circle
/**
* @type {Point}
*/
relCenter = [

@@ -360,11 +473,20 @@ circle.center[0] - sdata[4],

if (
// @ts-ignore
(prev.command == 'c' &&
// @ts-ignore
isConvex(prev.args) &&
// @ts-ignore
isArcPrev(prev.args, circle)) ||
// @ts-ignore
(prev.command == 'a' && prev.sdata && isArcPrev(prev.sdata, circle))
) {
// @ts-ignore
arcCurves.unshift(prev);
// @ts-ignore
arc.base = prev.base;
// @ts-ignore
arc.args[5] = arc.coords[0] - arc.base[0];
// @ts-ignore
arc.args[6] = arc.coords[1] - arc.base[1];
// @ts-ignore
var prevData = prev.command == 'a' ? prev.sdata : prev.args;

@@ -406,4 +528,7 @@ var prevAngle = findArcAngle(prevData, {

// less than 360°
// @ts-ignore
arc.coords = next.coords;
// @ts-ignore
arc.args[5] = arc.coords[0] - arc.base[0];
// @ts-ignore
arc.args[6] = arc.coords[1] - arc.base[1];

@@ -414,4 +539,7 @@ } else {

arc.args[6] = 2 * (relCircle.center[1] - nextData[5]);
// @ts-ignore
arc.coords = [
// @ts-ignore
arc.base[0] + arc.args[5],
// @ts-ignore
arc.base[1] + arc.args[6],

@@ -427,6 +555,10 @@ ];

sweep,
// @ts-ignore
next.coords[0] - arc.coords[0],
// @ts-ignore
next.coords[1] - arc.coords[1],
],
// @ts-ignore
coords: next.coords,
// @ts-ignore
base: arc.coords,

@@ -449,11 +581,19 @@ };

var prevArc = output.shift();
// @ts-ignore
roundData(prevArc.args);
// @ts-ignore
relSubpoint[0] += prevArc.args[5] - prev.args[prev.args.length - 2];
// @ts-ignore
relSubpoint[1] += prevArc.args[6] - prev.args[prev.args.length - 1];
// @ts-ignore
prev.command = 'a';
// @ts-ignore
prev.args = prevArc.args;
// @ts-ignore
item.base = prev.coords = prevArc.coords;
}
// @ts-ignore
arc = output.shift();
if (arcCurves.length == 1) {
// @ts-ignore
item.sdata = sdata.slice(); // preserve curve data for future checks

@@ -464,2 +604,3 @@ } else if (arcCurves.length - 1 - hasPrev > 0) {

path,
// @ts-ignore
[index + 1, arcCurves.length - 1 - hasPrev].concat(output)

@@ -471,2 +612,3 @@ );

data = arc.args;
// @ts-ignore
item.coords = arc.coords;

@@ -489,10 +631,15 @@ }

for (var i = data.length; i--; ) {
// @ts-ignore
data[i] += item.base[i % 2] - relSubpoint[i % 2];
}
} else if (command == 'h') {
// @ts-ignore
data[0] += item.base[0] - relSubpoint[0];
} else if (command == 'v') {
// @ts-ignore
data[0] += item.base[1] - relSubpoint[1];
} else if (command == 'a') {
// @ts-ignore
data[5] += item.base[0] - relSubpoint[0];
// @ts-ignore
data[6] += item.base[1] - relSubpoint[1];

@@ -531,3 +678,5 @@ }

command === 't' &&
// @ts-ignore
prev.command !== 'q' &&
// @ts-ignore
prev.command !== 't'

@@ -562,12 +711,19 @@ ) {

(command === 'm' || command === 'h' || command === 'v') &&
// @ts-ignore
prev.command &&
// @ts-ignore
command == prev.command.toLowerCase() &&
((command != 'h' && command != 'v') ||
// @ts-ignore
prev.args[0] >= 0 == data[0] >= 0)
) {
// @ts-ignore
prev.args[0] += data[0];
if (command != 'h' && command != 'v') {
// @ts-ignore
prev.args[1] += data[1];
}
// @ts-ignore
prev.coords = item.coords;
// @ts-ignore
path[index] = prev;

@@ -578,2 +734,3 @@ return false;

// convert curves into smooth shorthands
// @ts-ignore
if (params.curveSmoothShorthands && prev.command) {

@@ -584,4 +741,7 @@ // curveto

if (
// @ts-ignore
prev.command === 'c' &&
// @ts-ignore
data[0] === -(prev.args[2] - prev.args[4]) &&
// @ts-ignore
data[1] === -(prev.args[3] - prev.args[5])

@@ -595,4 +755,7 @@ ) {

else if (
// @ts-ignore
prev.command === 's' &&
// @ts-ignore
data[0] === -(prev.args[0] - prev.args[2]) &&
// @ts-ignore
data[1] === -(prev.args[1] - prev.args[3])

@@ -606,3 +769,5 @@ ) {

else if (
// @ts-ignore
prev.command !== 'c' &&
// @ts-ignore
prev.command !== 's' &&

@@ -621,4 +786,7 @@ data[0] === 0 &&

if (
// @ts-ignore
prev.command === 'q' &&
// @ts-ignore
data[0] === prev.args[2] - prev.args[0] &&
// @ts-ignore
data[1] === prev.args[3] - prev.args[1]

@@ -632,4 +800,7 @@ ) {

else if (
// @ts-ignore
prev.command === 't' &&
// @ts-ignore
data[2] === prev.args[0] &&
// @ts-ignore
data[3] === prev.args[1]

@@ -658,2 +829,3 @@ ) {

) {
// @ts-ignore
path[index] = prev;

@@ -665,2 +837,3 @@ return false;

if (command === 'a' && data[5] === 0 && data[6] === 0) {
// @ts-ignore
path[index] = prev;

@@ -679,2 +852,3 @@ return false;

relSubpoint[1] = pathBase[1];
// @ts-ignore
if (prev.command === 'Z' || prev.command === 'z') return false;

@@ -693,4 +867,3 @@ prev = item;

*
* @param {Array} data input path data
* @return {Boolean} output
* @type {(path: PathDataItem[], params: InternalParams) => PathDataItem[]}
*/

@@ -720,10 +893,15 @@ function convertToMixed(path, params) {

for (var i = adata.length; i--; ) {
// @ts-ignore
adata[i] += item.base[i % 2];
}
} else if (command == 'h') {
// @ts-ignore
adata[0] += item.base[0];
} else if (command == 'v') {
// @ts-ignore
adata[0] += item.base[1];
} else if (command == 'a') {
// @ts-ignore
adata[5] += item.base[0];
// @ts-ignore
adata[6] += item.base[1];

@@ -750,5 +928,7 @@ }

(data[0] < 0 ||
// @ts-ignore
(/^0\./.test(data[0]) && prev.args[prev.args.length - 1] % 1))
))
) {
// @ts-ignore
item.command = command.toUpperCase();

@@ -770,4 +950,3 @@ item.args = adata;

*
* @param {Array} data input path data
* @return {Boolean} output
* @type {(data: number[]) => boolean}
*/

@@ -787,3 +966,3 @@ function isConvex(data) {

return (
center &&
center != null &&
data[2] < center[0] == center[0] < 0 &&

@@ -799,4 +978,3 @@ data[3] < center[1] == center[1] < 0 &&

*
* @param {Array} coords 8 numbers for 4 pairs of coordinates (x,y)
* @return {Array|undefined} output coordinate of lines' crosspoint
* @type {(coords: number[]) => undefined | Point}
*/

@@ -816,2 +994,5 @@ function getIntersection(coords) {

/**
* @type {Point}
*/
var cross = [(b1 * c2 - b2 * c1) / denom, (a1 * c2 - a2 * c1) / -denom];

@@ -834,12 +1015,15 @@ if (

*
* @param {Array} data input data array
* @return {Array} output data array
* @type {(data: number[]) => number[]}
*/
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);
data[i] =
// @ts-ignore
+Math.abs(rounded - data[i]).toFixed(precision + 1) >= error
? +data[i].toFixed(precision)
? // @ts-ignore
+data[i].toFixed(precision)
: rounded;

@@ -854,4 +1038,3 @@ }

*
* @param {Array} data input data array
* @return {Array} output data array
* @type {(data: number[]) => number[]}
*/

@@ -869,5 +1052,3 @@ function round(data) {

*
* @param {Array} xs array of curve points x-coordinates
* @param {Array} ys array of curve points y-coordinates
* @return {Boolean}
* @type {(data: number[]) => boolean}
*/

@@ -896,4 +1077,3 @@

*
* @param {Object} item curve to convert
* @param {Array} data current curve data
* @type {(item: PathDataItem, data: number[]) => PathDataItem}
*/

@@ -920,5 +1100,3 @@

*
* @param {Array} point1 first point coordinates
* @param {Array} point2 second point coordinates
* @return {Number} distance
* @type {(point1: Point, point2: Point) => number}
*/

@@ -935,5 +1113,3 @@

*
* @param {Array} curve array of curve points coordinates
* @param {Number} t parametric position from 0 to 1
* @return {Array} Point coordinates
* @type {(curve: number[], t: number) => Point}
*/

@@ -956,4 +1132,3 @@

*
* @param {Array} curve
* @return {Object|undefined} circle
* @type {(curve: number[]) => undefined | Circle}
*/

@@ -976,2 +1151,3 @@

radius = center && getDistance([0, 0], center),
// @ts-ignore
tolerance = Math.min(arcThreshold * error, (arcTolerance * radius) / 100);

@@ -981,2 +1157,3 @@

center &&
// @ts-ignore
radius < 1e15 &&

@@ -986,2 +1163,3 @@ [1 / 4, 3 / 4].every(function (point) {

Math.abs(
// @ts-ignore
getDistance(getCubicBezierPoint(curve, point), center) - radius

@@ -992,2 +1170,3 @@ ) <= tolerance

)
// @ts-ignore
return { center: center, radius: radius };

@@ -999,5 +1178,3 @@ }

*
* @param {Object} circle
* @param {Array} curve
* @return {Boolean}
* @type {(curve: number[], circle: Circle) => boolean}
*/

@@ -1024,5 +1201,3 @@

*
* @param {Object} circle
* @param {Array} curve
* @return {Boolean}
* @type {(curve: number[], circle: Circle) => boolean}
*/

@@ -1040,5 +1215,3 @@

* @param {Array} curve
* @param {Object} relCircle
* @return {Number} angle
* @type {(curve: number[], relCircle: Circle) => number}
*/

@@ -1060,5 +1233,3 @@

*
* @param {Object} params
* @param {Array} pathData
* @return {String}
* @type {(params: InternalParams, pathData: PathDataItem[]) => string}
*/

@@ -1065,0 +1236,0 @@

@@ -11,4 +11,2 @@ 'use strict';

exports.name = 'convertShapeToPath';
exports.type = 'visitor';
exports.active = true;
exports.description = 'converts basic shapes to more compact path form';

@@ -15,0 +13,0 @@

'use strict';
const { attrsGroups } = require('./_collections');
exports.name = 'convertStyleToAttrs';
exports.type = 'perItem';
exports.active = false;
exports.description = 'converts style to attributes';
exports.params = {
keepImportant: false,
/**
* @type {(...args: string[]) => string}
*/
const g = (...args) => {
return '(?:' + args.join('|') + ')';
};
var stylingProps = require('./_collections').attrsGroups.presentation,
rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)', // Like \" or \2051. Code points consume one space.
rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*', // attribute name like ‘fill’
rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)", // string in single quotes: 'smth'
rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)', // string in double quotes: "smth"
rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$'),
// Parentheses, E.g.: url(data:image/png;base64,iVBO...).
// ':' and ';' inside of it should be threated as is. (Just like in strings.)
rParenthesis =
'\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)',
// The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input.
rValue =
'\\s*(' +
g(
'[^!\'"();\\\\]+?',
rEscape,
rSingleQuotes,
rQuotes,
rParenthesis,
'[^;]*?'
) +
'*?' +
')',
// End of declaration. Spaces outside of capturing groups help to do natural trimming.
rDeclEnd = '\\s*(?:;\\s*|$)',
// Important rule
rImportant = '(\\s*!important(?![-(\\w]))?',
// Final RegExp to parse CSS declarations.
regDeclarationBlock = new RegExp(
rAttr + ':' + rValue + rImportant + rDeclEnd,
'ig'
),
// Comments expression. Honors escape sequences and strings.
regStripComments = new RegExp(
g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'),
'ig'
);
const stylingProps = attrsGroups.presentation;
const rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)'; // Like \" or \2051. Code points consume one space.
const rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*'; // attribute name like ‘fill’
const rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)"; // string in single quotes: 'smth'
const rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)'; // string in double quotes: "smth"
const rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$');
// Parentheses, E.g.: url(data:image/png;base64,iVBO...).
// ':' and ';' inside of it should be threated as is. (Just like in strings.)
const rParenthesis =
'\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)';
// The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input.
const rValue =
'\\s*(' +
g(
'[^!\'"();\\\\]+?',
rEscape,
rSingleQuotes,
rQuotes,
rParenthesis,
'[^;]*?'
) +
'*?' +
')';
// End of declaration. Spaces outside of capturing groups help to do natural trimming.
const rDeclEnd = '\\s*(?:;\\s*|$)';
// Important rule
const rImportant = '(\\s*!important(?![-(\\w]))?';
// Final RegExp to parse CSS declarations.
const regDeclarationBlock = new RegExp(
rAttr + ':' + rValue + rImportant + rDeclEnd,
'ig'
);
// Comments expression. Honors escape sequences and strings.
const regStripComments = new RegExp(
g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'),
'ig'
);

@@ -66,68 +66,75 @@ /**

*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
* @author Kir Belevich
*
* @author Kir Belevich
* @type {import('../lib/types').Plugin<{
* keepImportant?: boolean
* }>}
*/
exports.fn = function (item, params) {
if (item.type === 'element' && item.attributes.style != null) {
// ['opacity: 1', 'color: #000']
let styles = [];
const newAttributes = {};
exports.fn = (_root, params) => {
const { keepImportant = false } = params;
return {
element: {
enter: (node) => {
if (node.attributes.style != null) {
// ['opacity: 1', 'color: #000']
let styles = [];
/**
* @type {Record<string, string>}
*/
const newAttributes = {};
// Strip CSS comments preserving escape sequences and strings.
const styleValue = item.attributes.style.replace(
regStripComments,
(match) => {
return match[0] == '/'
? ''
: match[0] == '\\' && /[-g-z]/i.test(match[1])
? match[1]
: match;
}
);
// Strip CSS comments preserving escape sequences and strings.
const styleValue = node.attributes.style.replace(
regStripComments,
(match) => {
return match[0] == '/'
? ''
: match[0] == '\\' && /[-g-z]/i.test(match[1])
? match[1]
: match;
}
);
regDeclarationBlock.lastIndex = 0;
// eslint-disable-next-line no-cond-assign
for (var rule; (rule = regDeclarationBlock.exec(styleValue)); ) {
if (!params.keepImportant || !rule[3]) {
styles.push([rule[1], rule[2]]);
}
}
regDeclarationBlock.lastIndex = 0;
// eslint-disable-next-line no-cond-assign
for (var rule; (rule = regDeclarationBlock.exec(styleValue)); ) {
if (!keepImportant || !rule[3]) {
styles.push([rule[1], rule[2]]);
}
}
if (styles.length) {
styles = styles.filter(function (style) {
if (style[0]) {
var prop = style[0].toLowerCase(),
val = style[1];
if (styles.length) {
styles = styles.filter(function (style) {
if (style[0]) {
var prop = style[0].toLowerCase(),
val = style[1];
if (rQuotedString.test(val)) {
val = val.slice(1, -1);
}
if (rQuotedString.test(val)) {
val = val.slice(1, -1);
}
if (stylingProps.includes(prop)) {
newAttributes[prop] = val;
if (stylingProps.includes(prop)) {
newAttributes[prop] = val;
return false;
}
}
return false;
}
}
return true;
});
return true;
});
Object.assign(item.attributes, newAttributes);
Object.assign(node.attributes, newAttributes);
if (styles.length) {
item.attributes.style = styles
.map((declaration) => declaration.join(':'))
.join(';');
} else {
delete item.attributes.style;
}
}
}
if (styles.length) {
node.attributes.style = styles
.map((declaration) => declaration.join(':'))
.join(';');
} else {
delete node.attributes.style;
}
}
}
},
},
};
};
function g() {
return '(?:' + Array.prototype.join.call(arguments, '|') + ')';
}

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

exports.type = 'visitor';
exports.name = 'convertTransform';
exports.active = true;
exports.description = 'collapses multiple transformations and optimizes it';

@@ -19,0 +17,0 @@

@@ -10,6 +10,7 @@ 'use strict';

const csstree = require('css-tree');
// @ts-ignore not defined in @types/csso
const specificity = require('csso/lib/restructure/prepare/specificity');
const stable = require('stable');
const {
// @ts-ignore not defined in @types/csso
syntax: { specificity },
} = require('csso');
const {
visitSkip,

@@ -20,5 +21,3 @@ querySelectorAll,

exports.type = 'visitor';
exports.name = 'inlineStyles';
exports.active = true;
exports.description = 'inline styles (additional options)';

@@ -28,3 +27,3 @@

* Compares two selector specificities.
* extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
* extracted from https://github.com/keeganstreet/specificity/blob/main/specificity.js#L211
*

@@ -45,2 +44,7 @@ * @type {(a: Specificity, b: Specificity) => number}

/**
* @type {(value: any) => any}
*/
const toAny = (value) => value;
/**
* Moves + merges styles from style elements to element styles

@@ -167,3 +171,3 @@ *

if (node.type === 'Selector') {
node.children.each((childNode, childItem, childList) => {
node.children.forEach((childNode, childItem, childList) => {
if (

@@ -208,7 +212,9 @@ childNode.type === 'PseudoClassSelector' ||

// 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();
const sortedSelectors = [...selectors]
.sort((a, b) => {
const aSpecificity = specificity(a.item.data);
const bSpecificity = specificity(b.item.data);
return compareSpecificity(aSpecificity, bSpecificity);
})
.reverse();

@@ -331,3 +337,7 @@ for (const selector of sortedSelectors) {

);
const firstSubSelector = selector.node.children.first();
/**
* csstree v2 changed this type
* @type {csstree.CssNode}
*/
const firstSubSelector = toAny(selector.node.children.first);
if (

@@ -365,3 +375,4 @@ firstSubSelector != null &&

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

@@ -373,3 +384,4 @@ list.remove(item);

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

@@ -376,0 +388,0 @@ detachNodeFromParent(style.node, style.parentNode);

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

exports.type = 'visitor';
exports.name = 'mergePaths';
exports.active = true;
exports.description = 'merges multiple paths in one if possible';

@@ -12,0 +10,0 @@

@@ -5,10 +5,8 @@ 'use strict';

* @typedef {import('../lib/types').XastElement} XastElement
* @typedef {import('../lib/types').XastChild} XastChild
*/
const { visitSkip, detachNodeFromParent } = require('../lib/xast.js');
const JSAPI = require('../lib/svgo/jsAPI.js');
exports.name = 'mergeStyles';
exports.type = 'visitor';
exports.active = true;
exports.description = 'merge multiple style elements into one';

@@ -29,2 +27,5 @@

let collectedStyles = '';
/**
* @type {'text' | 'cdata'}
*/
let styleContentType = 'text';

@@ -85,8 +86,12 @@

detachNodeFromParent(node, parentNode);
firstStyleElement.children = [
new JSAPI(
{ type: styleContentType, value: collectedStyles },
firstStyleElement
),
];
/**
* @type {XastChild}
*/
const child = { type: styleContentType, value: collectedStyles };
// TODO remove legacy parentNode in v4
Object.defineProperty(child, 'parentNode', {
writable: true,
value: firstStyleElement,
});
firstStyleElement.children = [child];
}

@@ -93,0 +98,0 @@ },

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

exports.type = 'visitor';
exports.name = 'minifyStyles';
exports.active = true;
exports.description =

@@ -14,0 +12,0 @@ 'minifies styles and removes unused styles based on usage data';

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

exports.type = 'visitor';
exports.name = 'moveElemsAttrsToGroup';
exports.active = true;
exports.description = 'Move common attributes of group children to the group';

@@ -11,0 +9,0 @@

@@ -6,7 +6,2 @@ 'use strict';

exports.name = 'moveGroupAttrsToElems';
exports.type = 'perItem';
exports.active = true;
exports.description = 'moves some group attributes to the content elements';

@@ -30,35 +25,42 @@

*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
* @author Kir Belevich
*
* @author Kir Belevich
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = function (item) {
// move group transform attr to content's pathElems
if (
item.type === 'element' &&
item.name === 'g' &&
item.children.length !== 0 &&
item.attributes.transform != null &&
Object.entries(item.attributes).some(
([name, value]) =>
referencesProps.includes(name) && value.includes('url(')
) === false &&
item.children.every(
(inner) =>
pathElemsWithGroupsAndText.includes(inner.name) &&
inner.attributes.id == null
)
) {
for (const inner of item.children) {
const value = item.attributes.transform;
if (inner.attributes.transform != null) {
inner.attributes.transform = value + ' ' + inner.attributes.transform;
} else {
inner.attributes.transform = value;
}
}
exports.fn = () => {
return {
element: {
enter: (node) => {
// move group transform attr to content's pathElems
if (
node.name === 'g' &&
node.children.length !== 0 &&
node.attributes.transform != null &&
Object.entries(node.attributes).some(
([name, value]) =>
referencesProps.includes(name) && value.includes('url(')
) === false &&
node.children.every(
(child) =>
child.type === 'element' &&
pathElemsWithGroupsAndText.includes(child.name) &&
child.attributes.id == null
)
) {
for (const child of node.children) {
const value = node.attributes.transform;
if (child.type === 'element') {
if (child.attributes.transform != null) {
child.attributes.transform = `${value} ${child.attributes.transform}`;
} else {
child.attributes.transform = value;
}
}
}
delete item.attributes.transform;
}
delete node.attributes.transform;
}
},
},
};
};

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

exports.type = 'visitor';
exports.name = 'prefixIds';
exports.active = false;
exports.description = 'prefix IDs';

@@ -73,2 +71,5 @@

/** @type {(value: any) => any} */
const toAny = (value) => value;
/**

@@ -145,13 +146,10 @@ * Prefixes identifiers

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

@@ -158,0 +156,0 @@ }

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

const minifyStyles = require('./minifyStyles.js');
const cleanupIDs = require('./cleanupIDs.js');
const cleanupIds = require('./cleanupIds.js');
const removeUselessDefs = require('./removeUselessDefs.js');

@@ -37,2 +37,3 @@ const cleanupNumericValues = require('./cleanupNumericValues.js');

const removeUnusedNS = require('./removeUnusedNS.js');
const sortAttrs = require('./sortAttrs.js');
const sortDefsChildren = require('./sortDefsChildren.js');

@@ -43,3 +44,3 @@ const removeTitle = require('./removeTitle.js');

const presetDefault = createPreset({
name: 'presetDefault',
name: 'preset-default',
plugins: [

@@ -55,3 +56,3 @@ removeDoctype,

minifyStyles,
cleanupIDs,
cleanupIds,
removeUselessDefs,

@@ -78,2 +79,3 @@ cleanupNumericValues,

removeUnusedNS,
sortAttrs,
sortDefsChildren,

@@ -80,0 +82,0 @@ removeTitle,

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeAttributesBySelector';
exports.type = 'visitor';
exports.active = false;
exports.description =

@@ -10,0 +8,0 @@ 'removes attributes of elements that match a css selector';

'use strict';
exports.name = 'removeAttrs';
exports.type = 'visitor';
exports.active = false;
exports.description = 'removes specified attributes';

@@ -7,0 +5,0 @@

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeComments';
exports.type = 'visitor';
exports.active = true;
exports.description = 'removes comments';

@@ -10,0 +8,0 @@

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeDesc';
exports.type = 'visitor';
exports.active = true;
exports.description = 'removes <desc>';

@@ -10,0 +8,0 @@

'use strict';
exports.name = 'removeDimensions';
exports.type = 'perItem';
exports.active = false;
exports.description =

@@ -20,25 +15,30 @@ 'removes width and height in presence of viewBox (opposite to removeViewBox, disable it first)';

*
* @param {Object} item current iteration item
* @return {Boolean} if true, with and height will be filtered out
* @author Benny Schudel
*
* @author Benny Schudel
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = function (item) {
if (item.type === 'element' && item.name === 'svg') {
if (item.attributes.viewBox != null) {
delete item.attributes.width;
delete item.attributes.height;
} else if (
item.attributes.width != null &&
item.attributes.height != null &&
Number.isNaN(Number(item.attributes.width)) === false &&
Number.isNaN(Number(item.attributes.height)) === false
) {
const width = Number(item.attributes.width);
const height = Number(item.attributes.height);
item.attributes.viewBox = `0 0 ${width} ${height}`;
delete item.attributes.width;
delete item.attributes.height;
}
}
exports.fn = () => {
return {
element: {
enter: (node) => {
if (node.name === 'svg') {
if (node.attributes.viewBox != null) {
delete node.attributes.width;
delete node.attributes.height;
} else if (
node.attributes.width != null &&
node.attributes.height != null &&
Number.isNaN(Number(node.attributes.width)) === false &&
Number.isNaN(Number(node.attributes.height)) === false
) {
const width = Number(node.attributes.width);
const height = Number(node.attributes.height);
node.attributes.viewBox = `0 0 ${width} ${height}`;
delete node.attributes.width;
delete node.attributes.height;
}
}
},
},
};
};

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeDoctype';
exports.type = 'visitor';
exports.active = true;
exports.description = 'removes doctype declaration';

@@ -10,0 +8,0 @@

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

exports.type = 'visitor';
exports.name = 'removeEditorsNSData';
exports.active = true;
exports.description = 'removes editors namespaces, elements and attributes';

@@ -11,0 +9,0 @@

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeElementsByAttr';
exports.type = 'visitor';
exports.active = false;
exports.description =

@@ -10,0 +8,0 @@ 'removes arbitrary elements by ID or className (disabled by default)';

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

exports.type = 'visitor';
exports.name = 'removeEmptyAttrs';
exports.active = true;
exports.description = 'removes empty attributes';

@@ -10,0 +8,0 @@

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

exports.type = 'visitor';
exports.name = 'removeEmptyContainers';
exports.active = true;
exports.description = 'removes empty container elements';

@@ -11,0 +9,0 @@

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeEmptyText';
exports.type = 'visitor';
exports.active = true;
exports.description = 'removes empty <text> elements';

@@ -10,0 +8,0 @@

'use strict';
const {
visit,
visitSkip,
querySelector,
closestByName,
detachNodeFromParent,

@@ -12,4 +13,2 @@ } = require('../lib/xast.js');

exports.name = 'removeHiddenElems';
exports.type = 'visitor';
exports.active = true;
exports.description =

@@ -71,2 +70,26 @@ 'removes hidden elements (zero sized, with absent attributes)';

visit(root, {
element: {
enter: (node, parentNode) => {
// transparent element inside clipPath still affect clipped elements
if (node.name === 'clipPath') {
return visitSkip;
}
const computedStyle = computeStyle(stylesheet, node);
// opacity="0"
//
// https://www.w3.org/TR/SVG11/masking.html#ObjectAndGroupOpacityProperties
if (
opacity0 &&
computedStyle.opacity &&
computedStyle.opacity.type === 'static' &&
computedStyle.opacity.value === '0'
) {
detachNodeFromParent(node, parentNode);
return;
}
},
},
});
return {

@@ -107,17 +130,2 @@ element: {

// opacity="0"
//
// https://www.w3.org/TR/SVG11/masking.html#ObjectAndGroupOpacityProperties
if (
opacity0 &&
computedStyle.opacity &&
computedStyle.opacity.type === 'static' &&
computedStyle.opacity.value === '0' &&
// transparent element inside clipPath still affect clipped elements
closestByName(node, 'clipPath') == null
) {
detachNodeFromParent(node, parentNode);
return;
}
// Circles with zero radius

@@ -124,0 +132,0 @@ //

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeMetadata';
exports.type = 'visitor';
exports.active = true;
exports.description = 'removes <metadata>';

@@ -10,0 +8,0 @@

'use strict';
exports.name = 'removeNonInheritableGroupAttrs';
exports.type = 'perItem';
exports.active = true;
exports.description =
'removes non-inheritable group’s presentational attributes';
const {

@@ -18,22 +9,31 @@ inheritableAttrs,

exports.name = 'removeNonInheritableGroupAttrs';
exports.description =
'removes non-inheritable group’s presentational attributes';
/**
* Remove non-inheritable group's "presentation" attributes.
*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
* @author Kir Belevich
*
* @author Kir Belevich
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = function (item) {
if (item.type === 'element' && item.name === 'g') {
for (const name of Object.keys(item.attributes)) {
if (
attrsGroups.presentation.includes(name) === true &&
inheritableAttrs.includes(name) === false &&
presentationNonInheritableGroupAttrs.includes(name) === false
) {
delete item.attributes[name];
}
}
}
exports.fn = () => {
return {
element: {
enter: (node) => {
if (node.name === 'g') {
for (const name of Object.keys(node.attributes)) {
if (
attrsGroups.presentation.includes(name) === true &&
inheritableAttrs.includes(name) === false &&
presentationNonInheritableGroupAttrs.includes(name) === false
) {
delete node.attributes[name];
}
}
}
},
},
};
};

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

exports.type = 'visitor';
exports.name = 'removeOffCanvasPaths';
exports.active = false;
exports.description =

@@ -16,0 +14,0 @@ 'removes elements that are drawn outside of the viewbox (disabled by default)';

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeRasterImages';
exports.type = 'visitor';
exports.active = false;
exports.description = 'removes raster images (disabled by default)';

@@ -10,0 +8,0 @@

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeScriptElement';
exports.type = 'visitor';
exports.active = false;
exports.description = 'removes <script> elements (disabled by default)';

@@ -10,0 +8,0 @@

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeStyleElement';
exports.type = 'visitor';
exports.active = false;
exports.description = 'removes <style> element (disabled by default)';

@@ -10,0 +8,0 @@

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeTitle';
exports.type = 'visitor';
exports.active = true;
exports.description = 'removes <title>';

@@ -10,0 +8,0 @@

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

exports.type = 'visitor';
exports.name = 'removeUnknownsAndDefaults';
exports.active = true;
exports.description =

@@ -18,0 +16,0 @@ 'removes unknown elements content and attributes, removes attrs with default values';

'use strict';
exports.type = 'visitor';
exports.name = 'removeUnusedNS';
exports.active = true;
exports.description = 'removes unused namespaces declaration';

@@ -7,0 +5,0 @@

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

exports.type = 'visitor';
exports.name = 'removeUselessDefs';
exports.active = true;
exports.description = 'removes elements in <defs> without id';

@@ -36,6 +34,8 @@

}
// TODO remove in SVGO 3
// TODO remove legacy parentNode in v4
for (const usefulNode of usefulNodes) {
// @ts-ignore parentNode is legacy
usefulNode.parentNode = node;
Object.defineProperty(usefulNode, 'parentNode', {
writable: true,
value: node,
});
}

@@ -42,0 +42,0 @@ node.children = usefulNodes;

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

exports.type = 'visitor';
exports.name = 'removeUselessStrokeAndFill';
exports.active = true;
exports.description = 'removes useless stroke and fill attributes';

@@ -12,0 +10,0 @@

'use strict';
exports.type = 'visitor';
exports.name = 'removeViewBox';
exports.active = true;
exports.description = 'removes viewBox attribute when possible';

@@ -7,0 +5,0 @@

'use strict';
exports.name = 'removeXMLNS';
exports.type = 'perItem';
exports.active = false;
exports.description =

@@ -20,12 +15,17 @@ 'removes xmlns attribute (for inline svg, disabled by default)';

*
* @param {Object} item current iteration item
* @return {Boolean} if true, xmlns will be filtered out
* @author Ricardo Tomasi
*
* @author Ricardo Tomasi
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = function (item) {
if (item.type === 'element' && item.name === 'svg') {
delete item.attributes.xmlns;
delete item.attributes['xmlns:xlink'];
}
exports.fn = () => {
return {
element: {
enter: (node) => {
if (node.name === 'svg') {
delete node.attributes.xmlns;
delete node.attributes['xmlns:xlink'];
}
},
},
};
};

@@ -6,4 +6,2 @@ 'use strict';

exports.name = 'removeXMLProcInst';
exports.type = 'visitor';
exports.active = true;
exports.description = 'removes XML processing instructions';

@@ -10,0 +8,0 @@

@@ -9,7 +9,3 @@ 'use strict';

const JSAPI = require('../lib/svgo/jsAPI.js');
exports.type = 'visitor';
exports.name = 'reusePaths';
exports.active = false;
exports.description =

@@ -56,3 +52,3 @@ 'Finds <path> elements with the same d, fill, and ' +

*/
const rawDefs = {
const defsTag = {
type: 'element',

@@ -63,6 +59,7 @@ name: 'defs',

};
/**
* @type {XastElement}
*/
const defsTag = new JSAPI(rawDefs, node);
// TODO remove legacy parentNode in v4
Object.defineProperty(defsTag, 'parentNode', {
writable: true,
value: node,
});
let index = 0;

@@ -75,3 +72,3 @@ for (const list of paths.values()) {

*/
const rawPath = {
const reusablePath = {
type: 'element',

@@ -82,16 +79,17 @@ name: 'path',

};
delete rawPath.attributes.transform;
delete reusablePath.attributes.transform;
let id;
if (rawPath.attributes.id == null) {
if (reusablePath.attributes.id == null) {
id = 'reuse-' + index;
index += 1;
rawPath.attributes.id = id;
reusablePath.attributes.id = id;
} else {
id = rawPath.attributes.id;
id = reusablePath.attributes.id;
delete list[0].attributes.id;
}
/**
* @type {XastElement}
*/
const reusablePath = new JSAPI(rawPath, defsTag);
// TODO remove legacy parentNode in v4
Object.defineProperty(reusablePath, 'parentNode', {
writable: true,
value: defsTag,
});
defsTag.children.push(reusablePath);

@@ -98,0 +96,0 @@ // convert paths to <use>

'use strict';
exports.type = 'visitor';
exports.name = 'sortAttrs';
exports.active = false;
exports.description = 'Sort element attributes for better compression';

@@ -7,0 +5,0 @@

'use strict';
exports.type = 'visitor';
exports.name = 'sortDefsChildren';
exports.active = true;
exports.description = 'Sorts children of <defs> to improve compression';

@@ -7,0 +5,0 @@

@@ -16,8 +16,5 @@ <div align="center">

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

@@ -29,14 +26,7 @@ ```

```sh
# Processing single files:
svgo one.svg two.svg -o one.min.svg two.min.svg
```
Or use the `--folder`/`-f` flag to optimize a whole folder of SVG icons
```sh
# Processing directory of svg files, recursively using `-f`, `--folder` :
svgo -f ./path/to/folder/with/svg/files -o ./path/to/folder/with/svg/output
```
See help for advanced usage
```sh
# Help for advanced usage
svgo --help

@@ -47,9 +37,10 @@ ```

Some options can be configured with CLI though it may be easier to have the configuration in a separate file.
SVGO automatically loads configuration from `svgo.config.js` or module specified with `--config` flag.
SVGO has a plugin-based architecture, separate plugins allows various xml svg optimizations. See [built-in plugins](#built-in-plugins).
SVGO automatically loads configuration from `svgo.config.js` or from `--config ./path/myconfig.js`. Some general options can be configured via CLI.
```js
// svgo.config.js
module.exports = {
multipass: true, // boolean. false by default
datauri: 'enc', // 'base64', 'enc' or 'unenc'. 'base64' by default
datauri: 'enc', // 'base64' (default), 'enc' or 'unenc'.
js2svg: {

@@ -59,24 +50,14 @@ indent: 2, // string with spaces or number of spaces. 4 by default

},
};
```
plugins: [
// set of built-in plugins enabled by default
'preset-default',
SVGO has a plugin-based architecture, so almost every optimization is a separate plugin.
There is a set of [built-in plugins](#built-in-plugins). See how to configure them:
```js
module.exports = {
plugins: [
// enable a built-in plugin by name
// enable built-in plugins by name
'prefixIds',
// or by expanded version
// or by expanded notation which allows to configure plugin
{
name: 'prefixIds',
},
// some plugins allow/require to pass options
{
name: 'prefixIds',
name: 'sortAttrs',
params: {
prefix: 'my-prefix',
xmlnsOrder: 'alphabetical',
},

@@ -88,5 +69,7 @@ },

The default preset of plugins is fully overridden if the `plugins` field is specified.
Use `preset-default` plugin to customize plugins options.
### Default preset
When extending default configuration specify `preset-default` plugin to enable optimisations.
Each plugin of default preset can be disabled or configured with "overrides" param.
```js

@@ -99,3 +82,3 @@ module.exports = {

overrides: {
// customize options for plugins included in preset
// customize default plugin options
inlineStyles: {

@@ -110,13 +93,2 @@ onlyMatchedOnce: false,

},
// enable builtin plugin not included in default preset
'prefixIds',
// enable and configure builtin plugin not included in preset
{
name: 'sortAttrs',
params: {
xmlnsOrder: 'alphabetical',
},
},
],

@@ -137,3 +109,3 @@ };

- minifyStyles
- cleanupIDs
- cleanupIds
- removeUselessDefs

@@ -164,2 +136,4 @@ - cleanupNumericValues

### Custom plugin
It's also possible to specify a custom plugin:

@@ -173,3 +147,2 @@

name: 'customPluginName',
type: 'perItem', // 'perItem', 'perItemReverse' or 'full'
params: {

@@ -220,52 +193,52 @@ optionName: 'optionValue',

| ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| [cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) | cleanup attributes from newlines, trailing, and repeating spaces | `enabled` |
| [mergeStyles](https://github.com/svg/svgo/blob/master/plugins/mergeStyles.js) | merge multiple style elements into one | `enabled` |
| [inlineStyles](https://github.com/svg/svgo/blob/master/plugins/inlineStyles.js) | move and merge styles from `<style>` elements to element `style` attributes | `enabled` |
| [removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) | remove `doctype` declaration | `enabled` |
| [removeXMLProcInst](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) | remove XML processing instructions | `enabled` |
| [removeComments](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) | remove comments | `enabled` |
| [removeMetadata](https://github.com/svg/svgo/blob/master/plugins/removeMetadata.js) | remove `<metadata>` | `enabled` |
| [removeTitle](https://github.com/svg/svgo/blob/master/plugins/removeTitle.js) | remove `<title>` | `enabled` |
| [removeDesc](https://github.com/svg/svgo/blob/master/plugins/removeDesc.js) | remove `<desc>` | `enabled` |
| [removeUselessDefs](https://github.com/svg/svgo/blob/master/plugins/removeUselessDefs.js) | remove elements of `<defs>` without `id` | `enabled` |
| [removeXMLNS](https://github.com/svg/svgo/blob/master/plugins/removeXMLNS.js) | removes the `xmlns` attribute (for inline SVG) | `disabled` |
| [removeEditorsNSData](https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js) | remove editors namespaces, elements, and attributes | `enabled` |
| [removeEmptyAttrs](https://github.com/svg/svgo/blob/master/plugins/removeEmptyAttrs.js) | remove empty attributes | `enabled` |
| [removeHiddenElems](https://github.com/svg/svgo/blob/master/plugins/removeHiddenElems.js) | remove hidden elements | `enabled` |
| [removeEmptyText](https://github.com/svg/svgo/blob/master/plugins/removeEmptyText.js) | remove empty Text elements | `enabled` |
| [removeEmptyContainers](https://github.com/svg/svgo/blob/master/plugins/removeEmptyContainers.js) | remove empty Container elements | `enabled` |
| [removeViewBox](https://github.com/svg/svgo/blob/master/plugins/removeViewBox.js) | remove `viewBox` attribute when possible | `enabled` |
| [cleanupEnableBackground](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) | remove or cleanup `enable-background` attribute when possible | `enabled` |
| [minifyStyles](https://github.com/svg/svgo/blob/master/plugins/minifyStyles.js) | minify `<style>` elements content with [CSSO](https://github.com/css/csso) | `enabled` |
| [convertStyleToAttrs](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) | convert styles into attributes | `disabled` |
| [convertColors](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) | convert colors (from `rgb()` to `#rrggbb`, from `#rrggbb` to `#rgb`) | `enabled` |
| [convertPathData](https://github.com/svg/svgo/blob/master/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/master/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/master/plugins/removeUnknownsAndDefaults.js) | remove unknown elements content and attributes, remove attributes with default values | `enabled` |
| [removeNonInheritableGroupAttrs](https://github.com/svg/svgo/blob/master/plugins/removeNonInheritableGroupAttrs.js) | remove non-inheritable group's "presentation" attributes | `enabled` |
| [removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) | remove useless `stroke` and `fill` attributes | `enabled` |
| [removeUnusedNS](https://github.com/svg/svgo/blob/master/plugins/removeUnusedNS.js) | remove unused namespaces declaration | `enabled` |
| [prefixIds](https://github.com/svg/svgo/blob/master/plugins/prefixIds.js) | prefix IDs and classes with the SVG filename or an arbitrary string | `disabled` |
| [cleanupIDs](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js) | remove unused and minify used IDs | `enabled` |
| [cleanupNumericValues](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) | round numeric values to the fixed precision, remove default `px` units | `enabled` |
| [cleanupListOfValues](https://github.com/svg/svgo/blob/master/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/master/plugins/moveElemsAttrsToGroup.js) | move elements' attributes to their enclosing group | `enabled` |
| [moveGroupAttrsToElems](https://github.com/svg/svgo/blob/master/plugins/moveGroupAttrsToElems.js) | move some group attributes to the contained elements | `enabled` |
| [collapseGroups](https://github.com/svg/svgo/blob/master/plugins/collapseGroups.js) | collapse useless groups | `enabled` |
| [removeRasterImages](https://github.com/svg/svgo/blob/master/plugins/removeRasterImages.js) | remove raster images | `disabled` |
| [mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js) | merge multiple Paths into one | `enabled` |
| [convertShapeToPath](https://github.com/svg/svgo/blob/master/plugins/convertShapeToPath.js) | convert some basic shapes to `<path>` | `enabled` |
| [convertEllipseToCircle](https://github.com/svg/svgo/blob/master/plugins/convertEllipseToCircle.js) | convert non-eccentric `<ellipse>` to `<circle>` | `enabled` |
| [sortAttrs](https://github.com/svg/svgo/blob/master/plugins/sortAttrs.js) | sort element attributes for epic readability | `disabled` |
| [sortDefsChildren](https://github.com/svg/svgo/blob/master/plugins/sortDefsChildren.js) | sort children of `<defs>` in order to improve compression | `enabled` |
| [removeDimensions](https://github.com/svg/svgo/blob/master/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/master/plugins/removeAttrs.js) | remove attributes by pattern | `disabled` |
| [removeAttributesBySelector](https://github.com/svg/svgo/blob/master/plugins/removeAttributesBySelector.js) | removes attributes of elements that match a CSS selector | `disabled` |
| [removeElementsByAttr](https://github.com/svg/svgo/blob/master/plugins/removeElementsByAttr.js) | remove arbitrary elements by `ID` or `className` | `disabled` |
| [addClassesToSVGElement](https://github.com/svg/svgo/blob/master/plugins/addClassesToSVGElement.js) | add classnames to an outer `<svg>` element | `disabled` |
| [addAttributesToSVGElement](https://github.com/svg/svgo/blob/master/plugins/addAttributesToSVGElement.js) | adds attributes to an outer `<svg>` element | `disabled` |
| [removeOffCanvasPaths](https://github.com/svg/svgo/blob/master/plugins/removeOffCanvasPaths.js) | removes elements that are drawn outside of the viewbox | `disabled` |
| [removeStyleElement](https://github.com/svg/svgo/blob/master/plugins/removeStyleElement.js) | remove `<style>` elements | `disabled` |
| [removeScriptElement](https://github.com/svg/svgo/blob/master/plugins/removeScriptElement.js) | remove `<script>` elements | `disabled` |
| [reusePaths](https://github.com/svg/svgo/blob/master/plugins/reusePaths.js) | Find duplicated <path> elements and replace them with <use> links | `disabled` |
| [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` |

@@ -297,3 +270,3 @@ ## Other Ways to Use SVGO

| [<img src="https://sheetjs.com/sketch128.png" width="80">](https://sheetjs.com/) | [<img src="https://raw.githubusercontent.com/fontello/fontello/master/fontello-image.svg" width="80">](https://fontello.com/) |
| [<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/) |
| :------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------: |

@@ -304,4 +277,4 @@ | [SheetJS LLC](https://sheetjs.com/) | [Fontello](https://fontello.com/) |

This software is released under the terms of the [MIT license](https://github.com/svg/svgo/blob/master/LICENSE).
This software is released under the terms of the [MIT license](https://github.com/svg/svgo/blob/main/LICENSE).
Logo by [André Castillo](https://github.com/DerianAndre).

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