svgo
Advanced tools
Comparing version 2.6.1 to 2.7.0
@@ -6,2 +6,3 @@ 'use strict'; | ||
* @typedef {import('./types').Specificity} Specificity | ||
* @typedef {import('./types').Stylesheet} Stylesheet | ||
* @typedef {import('./types').StylesheetRule} StylesheetRule | ||
@@ -132,3 +133,3 @@ * @typedef {import('./types').StylesheetDeclaration} StylesheetDeclaration | ||
/** | ||
* @type {(stylesheet: Array<StylesheetRule>, node: XastElement) => ComputedStyles} | ||
* @type {(stylesheet: Stylesheet, node: XastElement) => ComputedStyles} | ||
*/ | ||
@@ -151,3 +152,3 @@ const computeOwnStyle = (stylesheet, node) => { | ||
// collect matching rules | ||
for (const { selectors, declarations, dynamic } of stylesheet) { | ||
for (const { selectors, declarations, dynamic } of stylesheet.rules) { | ||
if (matches(node, selectors)) { | ||
@@ -217,3 +218,3 @@ for (const { name, value, important } of declarations) { | ||
/** | ||
* @type {(root: XastRoot) => Array<StylesheetRule>} | ||
* @type {(root: XastRoot) => Stylesheet} | ||
*/ | ||
@@ -224,7 +225,13 @@ const collectStylesheet = (root) => { | ||
*/ | ||
const stylesheet = []; | ||
// find and parse all styles | ||
const rules = []; | ||
/** | ||
* @type {Map<XastElement, XastParent>} | ||
*/ | ||
const parents = new Map(); | ||
visit(root, { | ||
element: { | ||
enter: (node) => { | ||
enter: (node, parentNode) => { | ||
// store parents | ||
parents.set(node, parentNode); | ||
// find and parse all styles | ||
if (node.name === 'style') { | ||
@@ -241,3 +248,3 @@ const dynamic = | ||
if (child.type === 'text' || child.type === 'cdata') { | ||
stylesheet.push(...parseStylesheet(child.value, dynamic)); | ||
rules.push(...parseStylesheet(child.value, dynamic)); | ||
} | ||
@@ -251,6 +258,6 @@ } | ||
// sort by selectors specificity | ||
stable.inplace(stylesheet, (a, b) => | ||
stable.inplace(rules, (a, b) => | ||
compareSpecificity(a.specificity, b.specificity) | ||
); | ||
return stylesheet; | ||
return { rules, parents }; | ||
}; | ||
@@ -260,12 +267,11 @@ exports.collectStylesheet = collectStylesheet; | ||
/** | ||
* @type {(stylesheet: Array<StylesheetRule>, node: XastElement) => ComputedStyles} | ||
* @type {(stylesheet: Stylesheet, node: XastElement) => ComputedStyles} | ||
*/ | ||
const computeStyle = (stylesheet, node) => { | ||
const { parents } = stylesheet; | ||
// collect inherited styles | ||
const computedStyles = computeOwnStyle(stylesheet, node); | ||
let parent = node; | ||
// @ts-ignore parentNode is forbidden in public usage | ||
while (parent.parentNode && parent.parentNode.type !== 'root') { | ||
// @ts-ignore parentNode is forbidden in public usage | ||
const inheritedStyles = computeOwnStyle(stylesheet, parent.parentNode); | ||
let parent = parents.get(node); | ||
while (parent != null && parent.type !== 'root') { | ||
const inheritedStyles = computeOwnStyle(stylesheet, parent); | ||
for (const [name, computed] of Object.entries(inheritedStyles)) { | ||
@@ -281,4 +287,3 @@ if ( | ||
} | ||
// @ts-ignore parentNode is forbidden in public usage | ||
parent = parent.parentNode; | ||
parent = parents.get(parent); | ||
} | ||
@@ -285,0 +290,0 @@ return computedStyles; |
@@ -5,2 +5,3 @@ 'use strict'; | ||
const fs = require('fs'); | ||
const { pathToFileURL } = require('url'); | ||
const path = require('path'); | ||
@@ -17,3 +18,21 @@ const { | ||
const importConfig = async (configFile) => { | ||
const config = require(configFile); | ||
let config; | ||
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; | ||
} | ||
} | ||
} | ||
if (config == null || typeof config !== 'object' || Array.isArray(config)) { | ||
@@ -45,6 +64,14 @@ throw Error(`Invalid config file "${configFile}"`); | ||
while (true) { | ||
const file = path.join(dir, 'svgo.config.js'); | ||
if (await isFile(file)) { | ||
return await importConfig(file); | ||
const js = path.join(dir, 'svgo.config.js'); | ||
if (await isFile(js)) { | ||
return await importConfig(js); | ||
} | ||
const mjs = path.join(dir, 'svgo.config.mjs'); | ||
if (await isFile(mjs)) { | ||
return await importConfig(mjs); | ||
} | ||
const cjs = path.join(dir, 'svgo.config.cjs'); | ||
if (await isFile(cjs)) { | ||
return await importConfig(cjs); | ||
} | ||
const parent = path.dirname(dir); | ||
@@ -51,0 +78,0 @@ if (dir === parent) { |
@@ -8,3 +8,3 @@ 'use strict'; | ||
} = require('./svgo/config.js'); | ||
const svg2js = require('./svgo/svg2js.js'); | ||
const { parseSvg } = require('./parser.js'); | ||
const js2svg = require('./svgo/js2svg.js'); | ||
@@ -35,3 +35,3 @@ const { invokePlugins } = require('./svgo/plugins.js'); | ||
try { | ||
svgjs = svg2js(input, config.path); | ||
svgjs = parseSvg(input, config.path); | ||
} catch (error) { | ||
@@ -38,0 +38,0 @@ return { error: error.toString(), modernError: error }; |
@@ -5,3 +5,3 @@ 'use strict'; | ||
const PATH = require('path'); | ||
const { green, red } = require('colorette'); | ||
const { green, red } = require('nanocolors'); | ||
const { loadConfig, optimize } = require('../svgo-node.js'); | ||
@@ -11,2 +11,3 @@ const pluginsMap = require('../../plugins/plugins.js'); | ||
const { encodeSVGDatauri, decodeSVGDatauri } = require('./tools.js'); | ||
const regSVGFile = /\.svg$/i; | ||
@@ -13,0 +14,0 @@ |
@@ -1,2 +0,2 @@ | ||
type XastDoctype = { | ||
export type XastDoctype = { | ||
type: 'doctype'; | ||
@@ -9,3 +9,3 @@ name: string; | ||
type XastInstruction = { | ||
export type XastInstruction = { | ||
type: 'instruction'; | ||
@@ -16,3 +16,3 @@ name: string; | ||
type XastComment = { | ||
export type XastComment = { | ||
type: 'comment'; | ||
@@ -22,3 +22,3 @@ value: string; | ||
type XastCdata = { | ||
export type XastCdata = { | ||
type: 'cdata'; | ||
@@ -28,3 +28,3 @@ value: string; | ||
type XastText = { | ||
export type XastText = { | ||
type: 'text'; | ||
@@ -104,2 +104,7 @@ value: string; | ||
export type Stylesheet = { | ||
rules: Array<StylesheetRule>; | ||
parents: Map<XastElement, XastParent>; | ||
}; | ||
type StaticStyle = { | ||
@@ -106,0 +111,0 @@ type: 'static'; |
{ | ||
"name": "svgo", | ||
"version": "2.6.1", | ||
"version": "2.7.0", | ||
"description": "Nodejs-based tool for optimizing SVG vector graphics files", | ||
@@ -53,3 +53,3 @@ "keywords": [ | ||
"scripts": { | ||
"test": "jest --coverage", | ||
"test": "NODE_OPTIONS=--experimental-vm-modules jest --maxWorkers=3 --coverage", | ||
"lint": "eslint --ignore-path .gitignore . && prettier --check \"**/*.js\" --ignore-path .gitignore", | ||
@@ -102,3 +102,2 @@ "fix": "eslint --ignore-path .gitignore --fix . && prettier --write \"**/*.js\" --ignore-path .gitignore", | ||
"@trysound/sax": "0.2.0", | ||
"colorette": "^1.4.0", | ||
"commander": "^7.2.0", | ||
@@ -108,2 +107,3 @@ "css-select": "^4.1.3", | ||
"csso": "^4.2.0", | ||
"nanocolors": "^0.1.12", | ||
"stable": "^0.1.8" | ||
@@ -120,3 +120,3 @@ }, | ||
"eslint": "^7.32.0", | ||
"jest": "^27.2.0", | ||
"jest": "^27.2.1", | ||
"mock-stdin": "^1.0.0", | ||
@@ -123,0 +123,0 @@ "node-fetch": "^2.6.2", |
'use strict'; | ||
const { inheritableAttrs, elemsGroups } = require('./_collections'); | ||
/** | ||
* @typedef {import('../lib/types').XastNode} XastNode | ||
*/ | ||
const { inheritableAttrs, elemsGroups } = require('./_collections.js'); | ||
exports.type = 'visitor'; | ||
exports.name = 'collapseGroups'; | ||
exports.type = 'perItemReverse'; | ||
exports.active = true; | ||
exports.description = 'collapses useless groups'; | ||
function hasAnimatedAttr(item, name) { | ||
if (item.type === 'element') { | ||
return ( | ||
(elemsGroups.animation.includes(item.name) && | ||
item.attributes.attributeName === name) || | ||
(item.children.length !== 0 && | ||
item.children.some((child) => hasAnimatedAttr(child, name))) | ||
); | ||
/** | ||
* @type {(node: XastNode, name: string) => boolean} | ||
*/ | ||
const hasAnimatedAttr = (node, name) => { | ||
if (node.type === 'element') { | ||
if ( | ||
elemsGroups.animation.includes(node.name) && | ||
node.attributes.attributeName === name | ||
) { | ||
return true; | ||
} | ||
for (const child of node.children) { | ||
if (hasAnimatedAttr(child, name)) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
}; | ||
/* | ||
/** | ||
* Collapse useless groups. | ||
@@ -43,50 +52,56 @@ * | ||
* | ||
* @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) { | ||
// non-empty elements | ||
if ( | ||
item.type === 'element' && | ||
item.name !== 'switch' && | ||
item.children.length !== 0 | ||
) { | ||
item.children.forEach(function (g, i) { | ||
// non-empty groups | ||
if (g.type === 'element' && g.name === 'g' && g.children.length !== 0) { | ||
exports.fn = () => { | ||
return { | ||
element: { | ||
exit: (node, parentNode) => { | ||
if (parentNode.type === 'root' || parentNode.name === 'switch') { | ||
return; | ||
} | ||
// non-empty groups | ||
if (node.name !== 'g' || node.children.length === 0) { | ||
return; | ||
} | ||
// move group attibutes to the single child element | ||
if (Object.keys(g.attributes).length !== 0 && g.children.length === 1) { | ||
var inner = g.children[0]; | ||
if ( | ||
Object.keys(node.attributes).length !== 0 && | ||
node.children.length === 1 | ||
) { | ||
const firstChild = node.children[0]; | ||
// TODO untangle this mess | ||
if ( | ||
inner.type === 'element' && | ||
inner.attributes.id == null && | ||
g.attributes.filter == null && | ||
(g.attributes.class == null || inner.attributes.class == null) && | ||
((g.attributes['clip-path'] == null && g.attributes.mask == null) || | ||
(inner.type === 'element' && | ||
inner.name === 'g' && | ||
g.attributes.transform == null && | ||
inner.attributes.transform == null)) | ||
firstChild.type === 'element' && | ||
firstChild.attributes.id == null && | ||
node.attributes.filter == null && | ||
(node.attributes.class == null || | ||
firstChild.attributes.class == null) && | ||
((node.attributes['clip-path'] == null && | ||
node.attributes.mask == null) || | ||
(firstChild.name === 'g' && | ||
node.attributes.transform == null && | ||
firstChild.attributes.transform == null)) | ||
) { | ||
for (const [name, value] of Object.entries(g.attributes)) { | ||
if (g.children.some((item) => hasAnimatedAttr(item, name))) | ||
for (const [name, value] of Object.entries(node.attributes)) { | ||
// avoid copying to not conflict with animated attribute | ||
if (hasAnimatedAttr(firstChild, name)) { | ||
return; | ||
if (inner.attributes[name] == null) { | ||
inner.attributes[name] = value; | ||
} else if (name == 'transform') { | ||
inner.attributes[name] = value + ' ' + inner.attributes[name]; | ||
} else if (inner.attributes[name] === 'inherit') { | ||
inner.attributes[name] = value; | ||
} | ||
if (firstChild.attributes[name] == null) { | ||
firstChild.attributes[name] = value; | ||
} else if (name === 'transform') { | ||
firstChild.attributes[name] = | ||
value + ' ' + firstChild.attributes[name]; | ||
} else if (firstChild.attributes[name] === 'inherit') { | ||
firstChild.attributes[name] = value; | ||
} else if ( | ||
inheritableAttrs.includes(name) === false && | ||
inner.attributes[name] !== value | ||
firstChild.attributes[name] !== value | ||
) { | ||
return; | ||
} | ||
delete g.attributes[name]; | ||
delete node.attributes[name]; | ||
} | ||
@@ -97,11 +112,26 @@ } | ||
// collapse groups without attributes | ||
if ( | ||
Object.keys(g.attributes).length === 0 && | ||
!g.children.some((item) => item.isElem(elemsGroups.animation)) | ||
) { | ||
item.spliceContent(i, 1, g.children); | ||
if (Object.keys(node.attributes).length === 0) { | ||
// animation elements "add" attributes to group | ||
// group should be preserved | ||
for (const child of node.children) { | ||
if ( | ||
child.type === 'element' && | ||
elemsGroups.animation.includes(child.name) | ||
) { | ||
return; | ||
} | ||
} | ||
// replace current node with all its children | ||
const index = parentNode.children.indexOf(node); | ||
parentNode.children.splice(index, 1, ...node.children); | ||
// TODO remove in v3 | ||
for (const child of node.children) { | ||
// @ts-ignore parentNode is forbidden for public usage | ||
// and will be moved in v3 | ||
child.parentNode = parentNode; | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
}, | ||
}, | ||
}; | ||
}; |
'use strict'; | ||
const { closestByName, detachNodeFromParent } = require('../lib/xast.js'); | ||
/** | ||
* @typedef {import('../lib/types').XastElement} XastElement | ||
*/ | ||
const { visitSkip, detachNodeFromParent } = require('../lib/xast.js'); | ||
const JSAPI = require('../lib/svgo/jsAPI.js'); | ||
@@ -15,4 +19,9 @@ | ||
* @author strarsis <strarsis@gmail.com> | ||
* | ||
* @type {import('../lib/types').Plugin<void>} | ||
*/ | ||
exports.fn = () => { | ||
/** | ||
* @type {null | XastElement} | ||
*/ | ||
let firstStyleElement = null; | ||
@@ -22,67 +31,65 @@ let collectedStyles = ''; | ||
const enterElement = (node, parentNode) => { | ||
// collect style elements | ||
if (node.name !== 'style') { | ||
return; | ||
} | ||
return { | ||
element: { | ||
enter: (node, parentNode) => { | ||
// skip <foreignObject> content | ||
if (node.name === 'foreignObject') { | ||
return visitSkip; | ||
} | ||
// skip <style> with invalid type attribute | ||
if ( | ||
node.attributes.type != null && | ||
node.attributes.type !== '' && | ||
node.attributes.type !== 'text/css' | ||
) { | ||
return; | ||
} | ||
// collect style elements | ||
if (node.name !== 'style') { | ||
return; | ||
} | ||
// skip <foreignObject> content | ||
if (closestByName(node, 'foreignObject')) { | ||
return; | ||
} | ||
// skip <style> with invalid type attribute | ||
if ( | ||
node.attributes.type != null && | ||
node.attributes.type !== '' && | ||
node.attributes.type !== 'text/css' | ||
) { | ||
return; | ||
} | ||
// extract style element content | ||
let css = ''; | ||
for (const child of node.children) { | ||
if (child.type === 'text') { | ||
css += child.value; | ||
} | ||
if (child.type === 'cdata') { | ||
styleContentType = 'cdata'; | ||
css += child.value; | ||
} | ||
} | ||
// extract style element content | ||
let css = ''; | ||
for (const child of node.children) { | ||
if (child.type === 'text') { | ||
css += child.value; | ||
} | ||
if (child.type === 'cdata') { | ||
styleContentType = 'cdata'; | ||
css += child.value; | ||
} | ||
} | ||
// remove empty style elements | ||
if (css.trim().length === 0) { | ||
detachNodeFromParent(node, parentNode); | ||
return; | ||
} | ||
// remove empty style elements | ||
if (css.trim().length === 0) { | ||
detachNodeFromParent(node, parentNode); | ||
return; | ||
} | ||
// collect css and wrap with media query if present in attribute | ||
if (node.attributes.media == null) { | ||
collectedStyles += css; | ||
} else { | ||
collectedStyles += `@media ${node.attributes.media}{${css}}`; | ||
delete node.attributes.media; | ||
} | ||
// collect css and wrap with media query if present in attribute | ||
if (node.attributes.media == null) { | ||
collectedStyles += css; | ||
} else { | ||
collectedStyles += `@media ${node.attributes.media}{${css}}`; | ||
delete node.attributes.media; | ||
} | ||
// combine collected styles in the first style element | ||
if (firstStyleElement == null) { | ||
firstStyleElement = node; | ||
} else { | ||
detachNodeFromParent(node, parentNode); | ||
firstStyleElement.children = [ | ||
new JSAPI( | ||
{ type: styleContentType, value: collectedStyles }, | ||
firstStyleElement | ||
), | ||
]; | ||
} | ||
}; | ||
return { | ||
element: { | ||
enter: enterElement, | ||
// combine collected styles in the first style element | ||
if (firstStyleElement == null) { | ||
firstStyleElement = node; | ||
} else { | ||
detachNodeFromParent(node, parentNode); | ||
firstStyleElement.children = [ | ||
new JSAPI( | ||
{ type: styleContentType, value: collectedStyles }, | ||
firstStyleElement | ||
), | ||
]; | ||
} | ||
}, | ||
}, | ||
}; | ||
}; |
'use strict'; | ||
const { inheritableAttrs, pathElems } = require('./_collections'); | ||
const { visit } = require('../lib/xast.js'); | ||
const { inheritableAttrs, pathElems } = require('./_collections.js'); | ||
exports.type = 'visitor'; | ||
exports.name = 'moveElemsAttrsToGroup'; | ||
exports.type = 'perItemReverse'; | ||
exports.active = true; | ||
exports.description = 'Move common attributes of group children to the group'; | ||
exports.description = 'moves elements attributes to the existing group wrapper'; | ||
/** | ||
* Collapse content's intersected and inheritable | ||
* attributes to the existing group wrapper. | ||
* Move common attributes of group children to the group | ||
* | ||
@@ -32,96 +29,103 @@ * @example | ||
* | ||
* @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' && | ||
item.children.length > 1 | ||
) { | ||
var intersection = {}, | ||
hasTransform = false, | ||
hasClip = | ||
item.attributes['clip-path'] != null || item.attributes.mask != null, | ||
intersected = item.children.every(function (inner) { | ||
if ( | ||
inner.type === 'element' && | ||
Object.keys(inner.attributes).length !== 0 | ||
) { | ||
// don't mess with possible styles (hack until CSS parsing is implemented) | ||
if (inner.attributes.class) return false; | ||
if (!Object.keys(intersection).length) { | ||
intersection = inner.attributes; | ||
} else { | ||
intersection = intersectInheritableAttrs( | ||
intersection, | ||
inner.attributes | ||
); | ||
exports.fn = (root) => { | ||
// find if any style element is present | ||
let deoptimizedWithStyles = false; | ||
visit(root, { | ||
element: { | ||
enter: (node) => { | ||
if (node.name === 'style') { | ||
deoptimizedWithStyles = true; | ||
} | ||
}, | ||
}, | ||
}); | ||
if (!intersection) return false; | ||
} | ||
return { | ||
element: { | ||
exit: (node) => { | ||
// process only groups with more than 1 children | ||
if (node.name !== 'g' || node.children.length <= 1) { | ||
return; | ||
} | ||
return true; | ||
// deoptimize the plugin when style elements are present | ||
// selectors may rely on id, classes or tag names | ||
if (deoptimizedWithStyles) { | ||
return; | ||
} | ||
return false; | ||
}), | ||
allPath = item.children.every(function (inner) { | ||
return inner.isElem(pathElems); | ||
}); | ||
if (intersected) { | ||
item.children.forEach(function (g) { | ||
for (const [name, value] of Object.entries(intersection)) { | ||
if ((!allPath && !hasClip) || name !== 'transform') { | ||
delete g.attributes[name]; | ||
if (name === 'transform') { | ||
if (!hasTransform) { | ||
if (item.attributes.transform != null) { | ||
item.attributes.transform = | ||
item.attributes.transform + ' ' + value; | ||
} else { | ||
item.attributes.transform = value; | ||
/** | ||
* find common attributes in group children | ||
* @type {Map<string, string>} | ||
*/ | ||
const commonAttributes = new Map(); | ||
let initial = true; | ||
let everyChildIsPath = true; | ||
for (const child of node.children) { | ||
if (child.type === 'element') { | ||
if (pathElems.includes(child.name) === false) { | ||
everyChildIsPath = false; | ||
} | ||
if (initial) { | ||
initial = false; | ||
// collect all inheritable attributes from first child element | ||
for (const [name, value] of Object.entries(child.attributes)) { | ||
// consider only inheritable attributes | ||
if (inheritableAttrs.includes(name)) { | ||
commonAttributes.set(name, value); | ||
} | ||
hasTransform = true; | ||
} | ||
} else { | ||
item.attributes[name] = value; | ||
// exclude uncommon attributes from initial list | ||
for (const [name, value] of commonAttributes) { | ||
if (child.attributes[name] !== value) { | ||
commonAttributes.delete(name); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
}; | ||
/** | ||
* Intersect inheritable attributes. | ||
* | ||
* @param {Object} a first attrs object | ||
* @param {Object} b second attrs object | ||
* | ||
* @return {Object} intersected attrs object | ||
*/ | ||
function intersectInheritableAttrs(a, b) { | ||
var c = {}; | ||
// preserve transform on children when group has clip-path or mask | ||
if ( | ||
node.attributes['clip-path'] != null || | ||
node.attributes.mask != null | ||
) { | ||
commonAttributes.delete('transform'); | ||
} | ||
for (const [name, value] of Object.entries(a)) { | ||
if ( | ||
// eslint-disable-next-line no-prototype-builtins | ||
b.hasOwnProperty(name) && | ||
inheritableAttrs.includes(name) && | ||
value === b[name] | ||
) { | ||
c[name] = value; | ||
} | ||
} | ||
// preserve transform when all children are paths | ||
// so the transform could be applied to path data by other plugins | ||
if (everyChildIsPath) { | ||
commonAttributes.delete('transform'); | ||
} | ||
if (!Object.keys(c).length) return false; | ||
// add common children attributes to group | ||
for (const [name, value] of commonAttributes) { | ||
if (name === 'transform') { | ||
if (node.attributes.transform != null) { | ||
node.attributes.transform = `${node.attributes.transform} ${value}`; | ||
} else { | ||
node.attributes.transform = value; | ||
} | ||
} else { | ||
node.attributes[name] = value; | ||
} | ||
} | ||
return c; | ||
} | ||
// delete common attributes from children | ||
for (const child of node.children) { | ||
if (child.type === 'element') { | ||
for (const [name] of commonAttributes) { | ||
delete child.attributes[name]; | ||
} | ||
} | ||
} | ||
}, | ||
}, | ||
}; | ||
}; |
@@ -9,3 +9,16 @@ 'use strict'; | ||
const DEFAULT_SEPARATOR = ':'; | ||
const ENOATTRS = `Warning: The plugin "removeAttrs" requires the "attrs" parameter. | ||
It should have a pattern to remove, otherwise the plugin is a noop. | ||
Config example: | ||
plugins: [ | ||
{ | ||
name: "removeAttrs", | ||
params: { | ||
attrs: "(fill|stroke)" | ||
} | ||
} | ||
] | ||
`; | ||
/** | ||
@@ -81,3 +94,7 @@ * Remove attributes | ||
exports.fn = (root, params) => { | ||
// wrap into an array if params is not | ||
if (typeof params.attrs == 'undefined') { | ||
console.warn(ENOATTRS); | ||
return null; | ||
} | ||
const elemSeparator = | ||
@@ -84,0 +101,0 @@ typeof params.elemSeparator == 'string' |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
968747
15040
+ Addednanocolors@^0.1.12
+ Addednanocolors@0.1.12(transitive)
- Removedcolorette@^1.4.0
- Removedcolorette@1.4.0(transitive)