Optimising icon with SVGO

This function is part of icon manipulation functions in Iconify Tools.

Function runSVGO() optimises icon using popular SVG optimisation tool SVGO.

Usage

Function has the following parameters:

  • svg, SVG. Icon instance.
  • options, object. Options (optional).

Function is asynchronous. That means you need to handle it as Promise instance, usually by adding await before function call.

Options

There are two ways to set options:

  • Using custom list of SVGO plugins.
  • Toggle groups of plugins using several options.

Custom plugins list

You can set custom plugins using plugins property of options. Value is array of plugins, passed directly to SVGO (see SVGO documentation).

Example:

await runSVGO(svg, {
   plugins: ['convertStyleToAttrs', 'inlineStyles'],
   multipass: true,
});

Plugin options

You can also pick from preset list of plugins by setting these options:

  • keepShapes, boolean. If true, plugins that modify shapes are not ran. This is useful if you need to keep shapes as is, for example, when animating shapes.
  • cleanupIDs, string | false. Custom prefix for rewriting IDs, false to disable plugins that change IDs.

These options cannot be used together with plugins option.

Other options

Options that can be used with any options listed above:

  • multipass, boolean. If true, plugins are ran multiple times for better optimisation. Enabled by default.

Example

svgo.ts
import { SVG, runSVGO } from '@iconify/tools';

const reallyBadIcon = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:cc="http://creativecommons.org/ns#"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:svg="http://www.w3.org/2000/svg"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
  xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
  width="2048"
  height="2048"
  id="svg3891"
  version="1.1"
  inkscape:version="0.91 r13725"
  sodipodi:docname="trash.svg"
  inkscape:export-filename="/home/nikku/camunda/projects/bpmn.io/bpmn-font/raw/trash.png"
  inkscape:export-xdpi="0.88"
  inkscape:export-ydpi="0.88">
 <defs
    id="defs3893">
   <inkscape:path-effect
      effect="spiro"
      id="path-effect4094"
      is_visible="true" />
   <inkscape:path-effect
      effect="spiro"
      id="path-effect4094-0"
      is_visible="true" />
 </defs>
 <sodipodi:namedview
    id="base"
    pagecolor="#ffffff"
    bordercolor="#666666"
    borderopacity="1.0"
    inkscape:pageopacity="0.0"
    inkscape:pageshadow="2"
    inkscape:zoom="0.175"
    inkscape:cx="307.67263"
    inkscape:cy="1030.7415"
    inkscape:document-units="px"
    inkscape:current-layer="layer1-6"
    showgrid="false"
    inkscape:window-width="1596"
    inkscape:window-height="807"
    inkscape:window-x="0"
    inkscape:window-y="91"
    inkscape:window-maximized="0"
    inkscape:snap-page="false"
    inkscape:snap-object-midpoints="false"
    inkscape:snap-nodes="false"
    inkscape:snap-to-guides="false"
    inkscape:snap-grids="false" />
 <metadata
    id="metadata3896">
   <rdf:RDF>
     <cc:Work
        rdf:about="">
       <dc:format>image/svg+xml</dc:format>
       <dc:type
          rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
       <dc:title />
     </cc:Work>
   </rdf:RDF>
 </metadata>
 <g
    inkscape:label="Layer 1"
    inkscape:groupmode="layer"
    id="layer1"
    transform="translate(0,995.63783)">
   <g
      transform="matrix(96.752895,0,0,96.752895,55.328158,-100816.34)"
      id="layer1-6"
      inkscape:label="Layer 1"
      style="display:inline">
     <path
        style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
        d="m 3.4296875,1038.3672 1.3325877,12.7308 10.5912408,0 1.228186,-12.7284 -13.1520736,0 z m 1.4921875,1.3437 10.185547,0 -0.972656,10.0411 -8.1582035,0 z"
        id="rect4089"
        inkscape:connector-curvature="0"
        sodipodi:nodetypes="ccccccccccc" />
     <g
        id="g4275"
        transform="matrix(1,0,0,0.90111263,0,103.41515)">
       <path
          sodipodi:nodetypes="cc"
          inkscape:connector-curvature="0"
          inkscape:original-d="m 7.0333918,1040.9794 0.9432241,7.504"
          inkscape:path-effect="#path-effect4094"
          id="path4092"
          d="m 7.0333918,1040.9794 0.9432241,7.504"
          style="fill:none;stroke:#000000;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
       <path
          sodipodi:nodetypes="cc"
          inkscape:connector-curvature="0"
          inkscape:original-d="m 12.990235,1040.9794 -0.943224,7.504"
          inkscape:path-effect="#path-effect4094-0"
          id="path4092-2"
          d="m 12.990235,1040.9794 -0.943224,7.504"
          style="fill:none;stroke:#000000;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
     </g>
     <path
        style="fill:#000000;fill-opacity:1;stroke:none"
        d="m 7.2638322,1035.194 -4.2854023,1.2542 0,0.6276 14.0667651,0 0,-0.6276 -4.337726,-1.2542 z"
        id="rect4121"
        inkscape:connector-curvature="0"
        sodipodi:nodetypes="ccccccc" />
     <path
        style="display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.72291225;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
        d="m 7.6269598,1033.8929 4.7697062,0 0,1.737 -4.7697062,0 z"
        id="rect4121-6" />
   </g>
 </g>
</svg>`
;

(async () => {
   const svg = new SVG(reallyBadIcon);
   await runSVGO(svg);
   console.log(svg.toMinifiedString());
})();
Result:
<svg xmlns="http://www.w3.org/2000/svg" width="2048" height="2048" viewBox="0 0 2048 2048"><metadata/><path d="m3.43 1038.367 1.332 12.731h10.592l1.228-12.728H3.43zm1.492 1.344h10.185l-.972 10.041H5.977z" color="#000" enable-background="accumulate" font-family="sans-serif" overflow="visible" style="line-height:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;block-progression:tb;white-space:normal;isolation:auto;mix-blend-mode:normal" transform="translate(55.328 -99820.702) scale(96.7529)"/><path fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.344" d="m7.033 1040.98.944 7.503m5.013-7.503-.943 7.503" transform="matrix(96.7529 0 0 87.185 55.328 -89815)"/><path d="M758.141 337.32 343.458 458.648v60.76h1361.023v-60.76L1284.767 337.32z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="69.952" d="M793.262 211.444h461.512v168.06H793.262z"/></svg>

That example shows running SVGO on icon that has not been cleaned up and validated. Not all useless attributes have been removed and SVGO doesn't check for some content that should not be in icon, such as text, raster images and events.

Therefore all icons must be cleaned up after loading.

Same code with clean up:

svgo.ts
import { SVG, runSVGO, cleanupSVG } from '@iconify/tools';

const reallyBadIcon = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:cc="http://creativecommons.org/ns#"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:svg="http://www.w3.org/2000/svg"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
  xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
  width="2048"
  height="2048"
  id="svg3891"
  version="1.1"
  inkscape:version="0.91 r13725"
  sodipodi:docname="trash.svg"
  inkscape:export-filename="/home/nikku/camunda/projects/bpmn.io/bpmn-font/raw/trash.png"
  inkscape:export-xdpi="0.88"
  inkscape:export-ydpi="0.88">
 <defs
    id="defs3893">
   <inkscape:path-effect
      effect="spiro"
      id="path-effect4094"
      is_visible="true" />
   <inkscape:path-effect
      effect="spiro"
      id="path-effect4094-0"
      is_visible="true" />
 </defs>
 <sodipodi:namedview
    id="base"
    pagecolor="#ffffff"
    bordercolor="#666666"
    borderopacity="1.0"
    inkscape:pageopacity="0.0"
    inkscape:pageshadow="2"
    inkscape:zoom="0.175"
    inkscape:cx="307.67263"
    inkscape:cy="1030.7415"
    inkscape:document-units="px"
    inkscape:current-layer="layer1-6"
    showgrid="false"
    inkscape:window-width="1596"
    inkscape:window-height="807"
    inkscape:window-x="0"
    inkscape:window-y="91"
    inkscape:window-maximized="0"
    inkscape:snap-page="false"
    inkscape:snap-object-midpoints="false"
    inkscape:snap-nodes="false"
    inkscape:snap-to-guides="false"
    inkscape:snap-grids="false" />
 <metadata
    id="metadata3896">
   <rdf:RDF>
     <cc:Work
        rdf:about="">
       <dc:format>image/svg+xml</dc:format>
       <dc:type
          rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
       <dc:title />
     </cc:Work>
   </rdf:RDF>
 </metadata>
 <g
    inkscape:label="Layer 1"
    inkscape:groupmode="layer"
    id="layer1"
    transform="translate(0,995.63783)">
   <g
      transform="matrix(96.752895,0,0,96.752895,55.328158,-100816.34)"
      id="layer1-6"
      inkscape:label="Layer 1"
      style="display:inline">
     <path
        style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
        d="m 3.4296875,1038.3672 1.3325877,12.7308 10.5912408,0 1.228186,-12.7284 -13.1520736,0 z m 1.4921875,1.3437 10.185547,0 -0.972656,10.0411 -8.1582035,0 z"
        id="rect4089"
        inkscape:connector-curvature="0"
        sodipodi:nodetypes="ccccccccccc" />
     <g
        id="g4275"
        transform="matrix(1,0,0,0.90111263,0,103.41515)">
       <path
          sodipodi:nodetypes="cc"
          inkscape:connector-curvature="0"
          inkscape:original-d="m 7.0333918,1040.9794 0.9432241,7.504"
          inkscape:path-effect="#path-effect4094"
          id="path4092"
          d="m 7.0333918,1040.9794 0.9432241,7.504"
          style="fill:none;stroke:#000000;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
       <path
          sodipodi:nodetypes="cc"
          inkscape:connector-curvature="0"
          inkscape:original-d="m 12.990235,1040.9794 -0.943224,7.504"
          inkscape:path-effect="#path-effect4094-0"
          id="path4092-2"
          d="m 12.990235,1040.9794 -0.943224,7.504"
          style="fill:none;stroke:#000000;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
     </g>
     <path
        style="fill:#000000;fill-opacity:1;stroke:none"
        d="m 7.2638322,1035.194 -4.2854023,1.2542 0,0.6276 14.0667651,0 0,-0.6276 -4.337726,-1.2542 z"
        id="rect4121"
        inkscape:connector-curvature="0"
        sodipodi:nodetypes="ccccccc" />
     <path
        style="display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.72291225;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
        d="m 7.6269598,1033.8929 4.7697062,0 0,1.737 -4.7697062,0 z"
        id="rect4121-6" />
   </g>
 </g>
</svg>`
;

(async () => {
   const svg = new SVG(reallyBadIcon);

   // Clean up and validate icon
   await cleanupSVG(svg);

   // Optimise icon
   await runSVGO(svg);

   console.log(svg.toMinifiedString());
})();
Result:
<svg xmlns="http://www.w3.org/2000/svg" width="2048" height="2048" viewBox="0 0 2048 2048"><path d="m387.19 644.317 128.875 1231.76h1024.807l118.813-1231.47H387.19zm144.356 130.035h985.428l-94.044 971.496H633.62z" color="#000"/><path fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.344" d="m7.033 1040.98.944 7.503m5.013-7.503-.943 7.503" transform="matrix(96.7529 0 0 87.185 55.328 -89815)"/><path d="M758.141 337.32 343.458 458.648v60.76h1361.023v-60.76L1284.767 337.32z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="69.952" d="M793.262 211.444h461.512v168.06H793.262z"/></svg>