svgo
Advanced tools
Comparing version 2.8.0 to 3.0.0
@@ -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)) { |
101
lib/svgo.js
'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 @@ |
179
README.md
@@ -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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
1491654
6
43854
3
1
75
268
+ Addedcss-select@5.1.0(transitive)
+ Addedcss-tree@2.2.12.3.1(transitive)
+ Addedcsso@5.0.5(transitive)
+ Addeddom-serializer@2.0.0(transitive)
+ Addeddomhandler@5.0.3(transitive)
+ Addeddomutils@3.1.0(transitive)
+ Addedentities@4.5.0(transitive)
+ Addedmdn-data@2.0.282.0.30(transitive)
+ Addedsource-map-js@1.2.0(transitive)
- Removedstable@^0.1.8
- Removedcss-select@4.3.0(transitive)
- Removedcss-tree@1.1.3(transitive)
- Removedcsso@4.2.0(transitive)
- Removeddom-serializer@1.4.1(transitive)
- Removeddomhandler@4.3.1(transitive)
- Removeddomutils@2.8.0(transitive)
- Removedentities@2.2.0(transitive)
- Removedmdn-data@2.0.14(transitive)
- Removedsource-map@0.6.1(transitive)
- Removedstable@0.1.8(transitive)
Updatedcss-select@^5.1.0
Updatedcss-tree@^2.2.1
Updatedcsso@^5.0.5