1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-29 20:21:14 +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';
/**
* @param {any} node
* @return {node is any}
*/
const isTag = (node) => {
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';
/**
* @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 xastAdaptor = require('./svgo/css-select-adapter.js');
@ -8,27 +15,40 @@ const cssSelectOptions = {
adapter: xastAdaptor,
};
/**
* @type {(node: XastNode, selector: string) => Array<XastChild>}
*/
const querySelectorAll = (node, selector) => {
return selectAll(selector, node, cssSelectOptions);
};
exports.querySelectorAll = querySelectorAll;
/**
* @type {(node: XastNode, selector: string) => null | XastChild}
*/
const querySelector = (node, selector) => {
return selectOne(selector, node, cssSelectOptions);
};
exports.querySelector = querySelector;
/**
* @type {(node: XastChild, selector: string) => boolean}
*/
const matches = (node, selector) => {
return is(node, selector, cssSelectOptions);
};
exports.matches = matches;
/**
* @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;
@ -38,6 +58,9 @@ exports.closestByName = closestByName;
const traverseBreak = Symbol();
exports.traverseBreak = traverseBreak;
/**
* @type {(node: any, fn: any) => any}
*/
const traverse = (node, fn) => {
if (fn(node) === traverseBreak) {
return traverseBreak;
@ -52,7 +75,10 @@ const traverse = (node, fn) => {
};
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];
if (callbacks && callbacks.enter) {
callbacks.enter(node, parentNode);
@ -78,6 +104,9 @@ const visit = (node, visitor, parentNode = null) => {
};
exports.visit = visit;
/**
* @type {(node: XastChild, parentNode: XastParent) => void}
*/
const detachNodeFromParent = (node, parentNode) => {
// avoid splice to not break for loops
parentNode.children = parentNode.children.filter((child) => child !== node);

View File

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

View File

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

View File

@ -14,6 +14,12 @@ const regSpaces = /\s{2,}/g;
* Cleanup attributes values from newlines, trailing and repeating spaces.
*
* @author Kir Belevich
*
* @type {import('../lib/types').Plugin<{
* newlines?: boolean,
* trim?: boolean,
* spaces?: boolean
* }>}
*/
exports.fn = (root, 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
*
* @author Taylor Hunt
*
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = () => {
return {
element: {
enter: (node) => {
if (node.name === 'ellipse') {
const rx = node.attributes.rx || 0;
const ry = node.attributes.ry || 0;
const rx = node.attributes.rx || '0';
const ry = node.attributes.ry || '0';
if (
rx === ry ||
rx === 'auto' ||

View File

@ -71,9 +71,11 @@ exports.description =
* ↓
* <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
*
* @type {import('../lib/types').Plugin<any>}
*/
exports.fn = (root, params) => {
const selectors = Array.isArray(params.selectors)
@ -82,12 +84,14 @@ exports.fn = (root, params) => {
for (const { selector, attributes } of selectors) {
const nodes = querySelectorAll(root, selector);
for (const node of nodes) {
if (Array.isArray(attributes)) {
for (const name of attributes) {
delete node.attributes[name];
if (node.type === 'element') {
if (Array.isArray(attributes)) {
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
*
* @param elemSeparator
* @example elemSeparator
* format: string
*
* @param preserveCurrentColor
* @example preserveCurrentColor
* format: boolean
*
* @param attrs:
* @example attrs:
*
* format: [ element* : attribute* : value* ]
*
@ -69,6 +69,12 @@ const DEFAULT_SEPARATOR = ':';
*
*
* @author Benny Schudel
*
* @type {import('../lib/types').Plugin<{
* elemSeparator?: string,
* preserveCurrentColor?: boolean,
* attrs: string | Array<string>
* }>}
*/
exports.fn = (root, params) => {
// 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) -->
*
* @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = () => {
return {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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