SVGO originally is optimizer. Though not all possible optimisation are implemented and sometimes custom transformation are required to solve problems.
SVGO v2 refreshed its plugins api with XAST specification and more useful visitor pattern.
Basic plugin looks like this
export const myPlugin = {
type: 'visitor',
name: 'pluginName',
fn: () => {
// do nothing
}
}
it currently does nothing but can be used in your svgo.config.js
import { myPlugin } from './myPlugin.js';
export default {
plugins: [
myPlugin
]
}
Visitor pattern allows to access all nodes in direct order from the root to the deepest one and reversed with enter
and exit
callbacks.
const myPlugin = {
type: 'visitor',
name: 'pluginName',
fn: () => {
return {
root: {
enter: node => { },
exit: node => { }
},
element: {
enter: node => { },
exit: node => { }
},
doctype: {
enter: node => { },
exit: node => { }
},
instruction: {
enter: node => { },
exit: node => { }
},
comment: {
enter: node => { },
exit: node => { }
},
cdata: {
enter: node => { },
exit: node => { }
},
text: {
enter: node => { },
exit: node => { }
}
}
}
}
All nodes except root also have an access to parentNode
const myPlugin = {
type: 'visitor',
name: 'pluginName',
fn: () => {
return {
element: {
enter: (node, parentNode) => {
parentNode.children.includes(node) // true
},
exit: (node, parentNode) => {
parentNode.children.includes(node) // true
}
}
}
}
}
To remove node from parent create new array with children
const myPlugin = {
type: 'visitor',
name: 'pluginName',
fn: () => {
return {
element: {
enter: (node, parentNode) => {
parentNode.children = parentNode.children.filter((child) => child !== node);
}
}
}
}
}
Find all circles with specific attribute
const myPlugin = {
type: 'visitor',
name: 'pluginName',
fn: () => {
return {
element: {
enter: (node, parentNode) => {
if (node.name === 'circle && node.attributes.fill != null) {
node.attributes.fill = 'modify in any way'
}
}
}
}
}
}
Collect all elements and analyze later
const myPlugin = {
type: 'visitor',
name: 'pluginName',
fn: () => {
const elements = []
return {
element: {
enter: (node, parentNode) => {
elements.push(node);
}
},
root: {
// root exit is called last
exit: () => {
for (const element of elements) {
// analyze and modify
}
}
}
}
}
}
This is the root of svg document containg all other nodes
XastRoot {
type: 'root',
children: Array<
| XastDoctype
| XastInstruction
| XastComment
| XastCdata
| XastText
| XastElement
>
}
Doctype is extracted from this markup
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
Where name is svg
and data.doctype is svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
.
XastDoctype {
type: 'doctype',
name: string,
data: {
doctype: string
}
}
Instruction is usually parse from this markup
<?xml version="1.0" encoding="utf-8"?>
Where name is xml
and value is version="1.0" encoding="utf-8"
XastInstruction {
type: 'instruction',
name: string,
value: string
}
Any xml comments like <!-- value -->
are parsed as the following node
XastComment {
type: 'comment',
value: string,
}
Text nodes
XastCdata {
type: 'cdata',
value: string,
}
XastText {
type: 'text',
value: string,
}
The element can contain any other node except root one
XastElement {
type: 'element',
name: string,
attributes: { [string]: string },
children: Array<
| XastDoctype
| XastInstruction
| XastComment
| XastCdata
| XastText
| XastElement
>
}