1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-31 07:44:22 +03:00

Add plugin types (#1527)

Covered following plugins
- addAttributesToSVGElement.js 
- addClassesToSVGElement.js 
- cleanupAttrs.js 
- convertEllipseToCircle.js 
- removeAttributesBySelector.js 
- removeAttrs.js 
- removeComments.js 
- removeDesc.js 
- removeDoctype.js 
- removeElementsByAttr.js 
- removeEmptyText.js 
- removeMetadata.js 
- removeRasterImages.js 
- removeScriptElement.js 
- removeStyleElement.js 
- removeTitle.js 
- removeXMLProcInst.js
This commit is contained in:
Bogdan Chadkin
2021-08-15 13:52:41 +03:00
committed by GitHub
parent 7ec255719c
commit 9b8f13e911
22 changed files with 188 additions and 50 deletions

2
lib/svgo/css-select-adapter.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
declare let obj: any;
export = obj;

View File

@ -1,9 +1,5 @@
'use strict'; 'use strict';
/**
* @param {any} node
* @return {node is any}
*/
const isTag = (node) => { const isTag = (node) => {
return node.type === 'element'; return node.type === 'element';
}; };

74
lib/types.ts Normal file
View File

@ -0,0 +1,74 @@
type XastDoctype = {
type: 'doctype';
name: string;
data: {
doctype: string;
};
};
type XastInstruction = {
type: 'instruction';
name: string;
value: string;
};
type XastComment = {
type: 'comment';
value: string;
};
type XastCdata = {
type: 'cdata';
value: string;
};
type XastText = {
type: 'text';
value: string;
};
type XastElement = {
type: 'element';
name: string;
attributes: Record<string, string>;
children: Array<XastChild>;
};
export type XastChild =
| XastDoctype
| XastInstruction
| XastComment
| XastCdata
| XastText
| XastElement;
type XastRoot = {
type: 'root';
children: Array<XastChild>;
};
export type XastParent = XastRoot | XastElement;
export type XastNode = XastRoot | XastChild;
type VisitorNode<Node> = {
enter?: (node: Node, parentNode: XastParent) => void;
leave?: (node: Node, parentNode: XastParent) => void;
};
type VisitorRoot = {
enter?: (node: XastRoot, parentNode: null) => void;
leave?: (node: XastRoot, parentNode: null) => void;
};
export type Visitor = {
doctype?: VisitorNode<XastDoctype>;
instruction?: VisitorNode<XastInstruction>;
comment?: VisitorNode<XastComment>;
cdata?: VisitorNode<XastCdata>;
text?: VisitorNode<XastText>;
element?: VisitorNode<XastElement>;
root?: VisitorRoot;
};
export type Plugin<Params> = (root: XastRoot, params: Params) => null | Visitor;

View File

@ -1,5 +1,12 @@
'use strict'; 'use strict';
/**
* @typedef {import('./types').XastNode} XastNode
* @typedef {import('./types').XastChild} XastChild
* @typedef {import('./types').XastParent} XastParent
* @typedef {import('./types').Visitor} Visitor
*/
const { selectAll, selectOne, is } = require('css-select'); const { selectAll, selectOne, is } = require('css-select');
const xastAdaptor = require('./svgo/css-select-adapter.js'); const xastAdaptor = require('./svgo/css-select-adapter.js');
@ -8,27 +15,40 @@ const cssSelectOptions = {
adapter: xastAdaptor, adapter: xastAdaptor,
}; };
/**
* @type {(node: XastNode, selector: string) => Array<XastChild>}
*/
const querySelectorAll = (node, selector) => { const querySelectorAll = (node, selector) => {
return selectAll(selector, node, cssSelectOptions); return selectAll(selector, node, cssSelectOptions);
}; };
exports.querySelectorAll = querySelectorAll; exports.querySelectorAll = querySelectorAll;
/**
* @type {(node: XastNode, selector: string) => null | XastChild}
*/
const querySelector = (node, selector) => { const querySelector = (node, selector) => {
return selectOne(selector, node, cssSelectOptions); return selectOne(selector, node, cssSelectOptions);
}; };
exports.querySelector = querySelector; exports.querySelector = querySelector;
/**
* @type {(node: XastChild, selector: string) => boolean}
*/
const matches = (node, selector) => { const matches = (node, selector) => {
return is(node, selector, cssSelectOptions); return is(node, selector, cssSelectOptions);
}; };
exports.matches = matches; exports.matches = matches;
/**
* @type {(node: XastChild, name: string) => null | XastChild}
*/
const closestByName = (node, name) => { const closestByName = (node, name) => {
let currentNode = node; let currentNode = node;
while (currentNode) { while (currentNode) {
if (currentNode.type === 'element' && currentNode.name === name) { if (currentNode.type === 'element' && currentNode.name === name) {
return currentNode; return currentNode;
} }
// @ts-ignore parentNode is hidden from public usage
currentNode = currentNode.parentNode; currentNode = currentNode.parentNode;
} }
return null; return null;
@ -38,6 +58,9 @@ exports.closestByName = closestByName;
const traverseBreak = Symbol(); const traverseBreak = Symbol();
exports.traverseBreak = traverseBreak; exports.traverseBreak = traverseBreak;
/**
* @type {(node: any, fn: any) => any}
*/
const traverse = (node, fn) => { const traverse = (node, fn) => {
if (fn(node) === traverseBreak) { if (fn(node) === traverseBreak) {
return traverseBreak; return traverseBreak;
@ -52,7 +75,10 @@ const traverse = (node, fn) => {
}; };
exports.traverse = traverse; exports.traverse = traverse;
const visit = (node, visitor, parentNode = null) => { /**
* @type {(node: any, visitor: any, parentNode: any) => void}
*/
const visit = (node, visitor, parentNode) => {
const callbacks = visitor[node.type]; const callbacks = visitor[node.type];
if (callbacks && callbacks.enter) { if (callbacks && callbacks.enter) {
callbacks.enter(node, parentNode); callbacks.enter(node, parentNode);
@ -78,6 +104,9 @@ const visit = (node, visitor, parentNode = null) => {
}; };
exports.visit = visit; exports.visit = visit;
/**
* @type {(node: XastChild, parentNode: XastParent) => void}
*/
const detachNodeFromParent = (node, parentNode) => { const detachNodeFromParent = (node, parentNode) => {
// avoid splice to not break for loops // avoid splice to not break for loops
parentNode.children = parentNode.children.filter((child) => child !== node); parentNode.children = parentNode.children.filter((child) => child !== node);

View File

@ -48,11 +48,16 @@ plugins: [
* Add attributes to an outer <svg> element. Example config: * Add attributes to an outer <svg> element. Example config:
* *
* @author April Arcus * @author April Arcus
*
* @type {import('../lib/types').Plugin<{
* attribute?: string | Record<string, null | string>,
* attributes?: Array<string | Record<string, null | string>>
* }>}
*/ */
exports.fn = (root, params) => { exports.fn = (root, params) => {
if (!Array.isArray(params.attributes) && !params.attribute) { if (!Array.isArray(params.attributes) && !params.attribute) {
console.error(ENOCLS); console.error(ENOCLS);
return; return null;
} }
const attributes = params.attributes || [params.attribute]; const attributes = params.attributes || [params.attribute];
return { return {
@ -62,12 +67,14 @@ exports.fn = (root, params) => {
for (const attribute of attributes) { for (const attribute of attributes) {
if (typeof attribute === 'string') { if (typeof attribute === 'string') {
if (node.attributes[attribute] == null) { if (node.attributes[attribute] == null) {
// @ts-ignore disallow explicit nullable attribute value
node.attributes[attribute] = undefined; node.attributes[attribute] = undefined;
} }
} }
if (typeof attribute === 'object') { if (typeof attribute === 'object') {
for (const key of Object.keys(attribute)) { for (const key of Object.keys(attribute)) {
if (node.attributes[key] == null) { if (node.attributes[key] == null) {
// @ts-ignore disallow explicit nullable attribute value
node.attributes[key] = attribute[key]; node.attributes[key] = attribute[key];
} }
} }

View File

@ -50,6 +50,11 @@ plugins: [
* ] * ]
* *
* @author April Arcus * @author April Arcus
*
* @type {import('../lib/types').Plugin<{
* className?: string,
* classNames?: Array<string>
* }>}
*/ */
exports.fn = (root, params) => { exports.fn = (root, params) => {
if ( if (
@ -57,13 +62,14 @@ exports.fn = (root, params) => {
!params.className !params.className
) { ) {
console.error(ENOCLS); console.error(ENOCLS);
return; return null;
} }
const classNames = params.classNames || [params.className]; const classNames = params.classNames || [params.className];
return { return {
element: { element: {
enter: (node, parentNode) => { enter: (node, parentNode) => {
if (node.name === 'svg' && parentNode.type === 'root') { if (node.name === 'svg' && parentNode.type === 'root') {
// @ts-ignore class attribute will be just a string eventually
node.class.add(...classNames); node.class.add(...classNames);
} }
}, },

View File

@ -14,6 +14,12 @@ const regSpaces = /\s{2,}/g;
* Cleanup attributes values from newlines, trailing and repeating spaces. * Cleanup attributes values from newlines, trailing and repeating spaces.
* *
* @author Kir Belevich * @author Kir Belevich
*
* @type {import('../lib/types').Plugin<{
* newlines?: boolean,
* trim?: boolean,
* spaces?: boolean
* }>}
*/ */
exports.fn = (root, params) => { exports.fn = (root, params) => {
const { newlines = true, trim = true, spaces = true } = params; const { newlines = true, trim = true, spaces = true } = params;

View File

@ -11,14 +11,16 @@ exports.description = 'converts non-eccentric <ellipse>s to <circle>s';
* @see https://www.w3.org/TR/SVG11/shapes.html * @see https://www.w3.org/TR/SVG11/shapes.html
* *
* @author Taylor Hunt * @author Taylor Hunt
*
* @type {import('../lib/types').Plugin<void>}
*/ */
exports.fn = () => { exports.fn = () => {
return { return {
element: { element: {
enter: (node) => { enter: (node) => {
if (node.name === 'ellipse') { if (node.name === 'ellipse') {
const rx = node.attributes.rx || 0; const rx = node.attributes.rx || '0';
const ry = node.attributes.ry || 0; const ry = node.attributes.ry || '0';
if ( if (
rx === ry || rx === ry ||
rx === 'auto' || rx === 'auto' ||

View File

@ -71,9 +71,11 @@ exports.description =
* ↓ * ↓
* <rect x="0" y="0" width="100" height="100"/> * <rect x="0" y="0" width="100" height="100"/>
* *
* @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|MDN CSS Selectors} * @link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|MDN CSS Selectors
* *
* @author Bradley Mease * @author Bradley Mease
*
* @type {import('../lib/types').Plugin<any>}
*/ */
exports.fn = (root, params) => { exports.fn = (root, params) => {
const selectors = Array.isArray(params.selectors) const selectors = Array.isArray(params.selectors)
@ -82,12 +84,14 @@ exports.fn = (root, params) => {
for (const { selector, attributes } of selectors) { for (const { selector, attributes } of selectors) {
const nodes = querySelectorAll(root, selector); const nodes = querySelectorAll(root, selector);
for (const node of nodes) { for (const node of nodes) {
if (Array.isArray(attributes)) { if (node.type === 'element') {
for (const name of attributes) { if (Array.isArray(attributes)) {
delete node.attributes[name]; for (const name of attributes) {
delete node.attributes[name];
}
} else {
delete node.attributes[attributes];
} }
} else {
delete node.attributes[attributes];
} }
} }
} }

View File

@ -10,13 +10,13 @@ const DEFAULT_SEPARATOR = ':';
/** /**
* Remove attributes * Remove attributes
* *
* @param elemSeparator * @example elemSeparator
* format: string * format: string
* *
* @param preserveCurrentColor * @example preserveCurrentColor
* format: boolean * format: boolean
* *
* @param attrs: * @example attrs:
* *
* format: [ element* : attribute* : value* ] * format: [ element* : attribute* : value* ]
* *
@ -69,6 +69,12 @@ const DEFAULT_SEPARATOR = ':';
* *
* *
* @author Benny Schudel * @author Benny Schudel
*
* @type {import('../lib/types').Plugin<{
* elemSeparator?: string,
* preserveCurrentColor?: boolean,
* attrs: string | Array<string>
* }>}
*/ */
exports.fn = (root, params) => { exports.fn = (root, params) => {
// wrap into an array if params is not // wrap into an array if params is not

View File

@ -15,6 +15,8 @@ exports.description = 'removes comments';
* Plug-In . SVG Version: 6.00 Build 0) --> * Plug-In . SVG Version: 6.00 Build 0) -->
* *
* @author Kir Belevich * @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/ */
exports.fn = () => { exports.fn = () => {
return { return {

View File

@ -17,6 +17,8 @@ const standardDescs = /^(Created with|Created using)/;
* https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc
* *
* @author Daniel Wabyick * @author Daniel Wabyick
*
* @type {import('../lib/types').Plugin<{ removeAny?: boolean }>}
*/ */
exports.fn = (root, params) => { exports.fn = (root, params) => {
const { removeAny = true } = params; const { removeAny = true } = params;

View File

@ -28,6 +28,8 @@ exports.description = 'removes doctype declaration';
* ]> * ]>
* *
* @author Kir Belevich * @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/ */
exports.fn = () => { exports.fn = () => {
return { return {

View File

@ -11,9 +11,7 @@ exports.description =
/** /**
* Remove arbitrary SVG elements by ID or className. * Remove arbitrary SVG elements by ID or className.
* *
* @param id * @example id
* examples:
*
* > single: remove element with ID of `elementID` * > single: remove element with ID of `elementID`
* --- * ---
* removeElementsByAttr: * removeElementsByAttr:
@ -26,9 +24,7 @@ exports.description =
* - 'elementID' * - 'elementID'
* - 'anotherID' * - 'anotherID'
* *
* @param class * @example class
* examples:
*
* > single: remove all elements with class of `elementClass` * > single: remove all elements with class of `elementClass`
* --- * ---
* removeElementsByAttr: * removeElementsByAttr:
@ -42,6 +38,11 @@ exports.description =
* - 'anotherClass' * - 'anotherClass'
* *
* @author Eli Dupuis (@elidupuis) * @author Eli Dupuis (@elidupuis)
*
* @type {import('../lib/types').Plugin<{
* id?: string | Array<string>,
* class?: string | Array<string>
* }>}
*/ */
exports.fn = (root, params) => { exports.fn = (root, params) => {
const ids = const ids =

View File

@ -23,6 +23,12 @@ exports.description = 'removes empty <text> elements';
* <tref xlink:href=""/> * <tref xlink:href=""/>
* *
* @author Kir Belevich * @author Kir Belevich
*
* @type {import('../lib/types').Plugin<{
* text?: boolean,
* tspan?: boolean,
* tref?: boolean
* }>}
*/ */
exports.fn = (root, params) => { exports.fn = (root, params) => {
const { text = true, tspan = true, tref = true } = params; const { text = true, tspan = true, tref = true } = params;

View File

@ -13,6 +13,8 @@ exports.description = 'removes <metadata>';
* https://www.w3.org/TR/SVG11/metadata.html * https://www.w3.org/TR/SVG11/metadata.html
* *
* @author Kir Belevich * @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/ */
exports.fn = () => { exports.fn = () => {
return { return {

View File

@ -13,6 +13,8 @@ exports.description = 'removes raster images (disabled by default)';
* @see https://bugs.webkit.org/show_bug.cgi?id=63548 * @see https://bugs.webkit.org/show_bug.cgi?id=63548
* *
* @author Kir Belevich * @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/ */
exports.fn = () => { exports.fn = () => {
return { return {

View File

@ -12,8 +12,9 @@ exports.description = 'removes <script> elements (disabled by default)';
* *
* https://www.w3.org/TR/SVG11/script.html * https://www.w3.org/TR/SVG11/script.html
* *
*
* @author Patrick Klingemann * @author Patrick Klingemann
*
* @type {import('../lib/types').Plugin<void>}
*/ */
exports.fn = () => { exports.fn = () => {
return { return {

View File

@ -13,6 +13,8 @@ exports.description = 'removes <style> element (disabled by default)';
* https://www.w3.org/TR/SVG11/styling.html#StyleElement * https://www.w3.org/TR/SVG11/styling.html#StyleElement
* *
* @author Betsy Dupuis * @author Betsy Dupuis
*
* @type {import('../lib/types').Plugin<void>}
*/ */
exports.fn = () => { exports.fn = () => {
return { return {

View File

@ -13,6 +13,8 @@ exports.description = 'removes <title>';
* https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title
* *
* @author Igor Kalashnikov * @author Igor Kalashnikov
*
* @type {import('../lib/types').Plugin<void>}
*/ */
exports.fn = () => { exports.fn = () => {
return { return {

View File

@ -14,6 +14,8 @@ exports.description = 'removes XML processing instructions';
* <?xml version="1.0" encoding="utf-8"?> * <?xml version="1.0" encoding="utf-8"?>
* *
* @author Kir Belevich * @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/ */
exports.fn = () => { exports.fn = () => {
return { return {

View File

@ -8,29 +8,19 @@
"checkJs": true, "checkJs": true,
"strict": true, "strict": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"noImplicitAny": false "noImplicitAny": true
}, },
"include": ["lib/**/*", "plugins/**/*", "test/**/*"], "include": ["plugins/**/*"],
"exclude": [ "exclude": [
"**/*.test.js",
"lib/svgo-node.js",
"lib/svgo/coa.js",
"lib/svgo/config.js",
"lib/svgo/js2svg.js",
"lib/svgo/svg2js.js",
"lib/svgo/jsAPI.js",
"lib/svgo.js",
"plugins/_path.js", "plugins/_path.js",
"plugins/_transforms.js", "plugins/_transforms.js",
"plugins/_applyTransforms.js", "plugins/_applyTransforms.js",
"plugins/cleanupAttrs.js",
"plugins/cleanupEnableBackground.js", "plugins/cleanupEnableBackground.js",
"plugins/cleanupIDs.js", "plugins/cleanupIDs.js",
"plugins/cleanupListOfValues.js", "plugins/cleanupListOfValues.js",
"plugins/cleanupNumericValues.js", "plugins/cleanupNumericValues.js",
"plugins/collapseGroups.js", "plugins/collapseGroups.js",
"plugins/convertColors.js", "plugins/convertColors.js",
"plugins/convertEllipseToCircle.js",
"plugins/convertPathData.js", "plugins/convertPathData.js",
"plugins/convertShapeToPath.js", "plugins/convertShapeToPath.js",
"plugins/convertStyleToAttrs.js", "plugins/convertStyleToAttrs.js",
@ -41,19 +31,12 @@
"plugins/moveGroupAttrsToElems.js", "plugins/moveGroupAttrsToElems.js",
"plugins/plugins.js", "plugins/plugins.js",
"plugins/prefixIds.js", "plugins/prefixIds.js",
"plugins/removeAttributesBySelector.js",
"plugins/removeAttrs.js",
"plugins/removeComments.js",
"plugins/removeDimensions.js", "plugins/removeDimensions.js",
"plugins/removeDoctype.js",
"plugins/removeEditorsNSData.js", "plugins/removeEditorsNSData.js",
"plugins/removeElementsByAttr.js",
"plugins/removeEmptyAttrs.js", "plugins/removeEmptyAttrs.js",
"plugins/removeEmptyText.js",
"plugins/removeHiddenElems.js", "plugins/removeHiddenElems.js",
"plugins/removeNonInheritableGroupAttrs.js", "plugins/removeNonInheritableGroupAttrs.js",
"plugins/removeOffCanvasPaths.js", "plugins/removeOffCanvasPaths.js",
"plugins/removeRasterImages.js",
"plugins/removeUnknownsAndDefaults.js", "plugins/removeUnknownsAndDefaults.js",
"plugins/removeUnusedNS.js", "plugins/removeUnusedNS.js",
"plugins/removeUselessDefs.js", "plugins/removeUselessDefs.js",
@ -62,11 +45,10 @@
"plugins/removeXMLNS.js", "plugins/removeXMLNS.js",
"plugins/reusePaths.js", "plugins/reusePaths.js",
"plugins/sortDefsChildren.js", "plugins/sortDefsChildren.js",
"plugins/preset-default.js", "plugins/sortAttrs.js",
"lib/svgo/jsAPI.js", "plugins/removeEmptyContainers.js",
"test/browser.js", "plugins/minifyStyles.js",
"test/config/fixtures/**/*.js", "plugins/inlineStyles.js",
"test/regression.js", "plugins/preset-default.js"
"lib/svgo/plugins.js"
] ]
} }