Skip to content

Cleaning up icons

All icon sets available with Iconify pass validation and clean up process.

Why is it needed? SVG is not like other images, it can contain a lot of things, making it hard to work with.

Often icons contain a lot of useless code (especially when exported from old software).

Icons can even contain malicious code, such as scripts, event listeners and links to external resources.

When working with monotone icons (icons that have only one color, which supposed to be changeable), some icons use fill, some stroke, some rely on system default colors, some use black color, some use seemingly random colors.

All the tools you need are available in Iconify Tools package.

Process

Clean up process is done in 3 steps:

  • Validation and initial clean up.
  • Changing icon palette.
  • Optimisation.

Validation and initial clean up

First part is code validation and initial clean up.

When a designer exports an icon from an editor, often SVG contains a lot of extra code that is not needed to display an icon.

Icons can also contain dangerous elements, such as scripts or external resources. Icons are validated to make sure they do not contain anything other than vector shapes.

See example of bad code and explanation of how SVG validation works.

Palette changes

After initial clean up and validation, the icon palette is changed.

For monotone icons, color is changed to currentColor to make sure the icon follows text color. This way it is easy to change color for any icon, regardless if the icon uses fill or stroke.

For icons with a hardcoded palette, the parser checks that the icon does not use system default colors or currentColor.

See article explaining palette changes.

Optimisation

Last step is optimisation. It reduces icon size by removing unnecessary code and optimising paths.

See a very short article explaining icon optimisation.

Code

Want to try it with your icons? All functions you need are available in Iconify Tools.

Below are several examples.

Parsing one monotone icon

This code sample parses one monotone icon. Icon uses black color, which is replaced with currentColor, shapes with white color are removed.

Icon is loaded from one file, output is stored in another file.

tsimport { promises as fs } from 'fs';
import { compareColors, stringToColor } from '@iconify/utils/lib/colors';
import {
   SVG,
   cleanupSVG,
   parseColors,
   isEmptyColor,
   runSVGO,
   deOptimisePaths,
} from '@iconify/tools';

(async () => {
   const source = 'svg/test.svg';
   const target = 'htdocs/assets/test.svg';

   // Load icon
   const content = await fs.readFile(source, 'utf8');
   const svg = new SVG(content);

   // Clean up and validate icon
   // This will throw an exception if icon is invalid
   cleanupSVG(svg);

   // Change color to `currentColor`
   // Skip this step if icon has hardcoded palette
   const blackColor = stringToColor('black');
   const whiteColor = stringToColor('white');
   parseColors(svg, {
       defaultColor: 'currentColor',
       callback: (attr, colorStr, color) => {
           if (!color) {
               // Color cannot be parsed!
               throw new Error(`Invalid color: "${colorStr}" in attribute ${attr}`);
           }

           if (isEmptyColor(color)) {
               // Color is empty: 'none' or 'transparent'. Return as is
               return color;
           }

           // Change black to 'currentColor'
           if (compareColors(color, blackColor)) {
               return 'currentColor';
           }

           // Remove shapes with white color
           if (compareColors(color, whiteColor)) {
               return 'remove';
           }

           throw new Error(`Unexpected color "${colorStr}" in attribute ${attr}`);
       },
   });

   // Optimise
   runSVGO(svg);

   // Update paths for compatibility with old software
   deOptimisePaths(svg);

   // Get SVG string. Returned <svg> has dimensions matching viewBox, such as height="24"
   const newContent = svg.toMinifiedString();

   // Same as above, but exported <svg> will have height="1em". Use this code if
   // you need height to be "1em", such as when using this code in unplugin-icons
   // const newContent = svg.toMinifiedString({ height: '1em' });

   // Save icon
   await fs.writeFile(target, newContent, 'utf8');
})();

Parsing an entire icon set

This code sample parses an entire icon set and returns icon set in IconifyJSON format.

It is similar to the example above, but uses importDirectory() to import all SVG files in a directory, then stores the result in a JSON file. Each icon is parsed in asynchronous forEach() callback.

tsimport { promises as fs } from 'fs';
import { compareColors, stringToColor } from '@iconify/utils/lib/colors';
import {
   cleanupSVG,
   parseColors,
   isEmptyColor,
   runSVGO,
   deOptimisePaths,
   importDirectory,
} from '@iconify/tools';

(async () => {
   const source = 'svg';
   const prefix = 'test';
   const target = 'htdocs/assets/test.json';

   // Load icon set
   const iconSet = await importDirectory(source, {
       // Set prefix for imported icon set to 'test'
       prefix
   });

   // Parse all icons
   await iconSet.forEach((name, type) => {
       if (type !== 'icon') {
           // Do not parse aliases
           return;
       }

       // Get SVG instance for icon
       const svg = iconSet.toSVG(name);

       // Clean up and validate icon
       // This will throw an exception if icon is invalid
       cleanupSVG(svg);

       // Change color to `currentColor`
       // Skip this step if icon has hardcoded palette
       const blackColor = stringToColor('black');
       const whiteColor = stringToColor('white');
       parseColors(svg, {
           defaultColor: 'currentColor',
           callback: (attr, colorStr, color) => {
               if (!color) {
                   // Color cannot be parsed!
                   throw new Error(`Invalid color: "${colorStr}" in attribute ${attr}`);
               }

               if (isEmptyColor(color)) {
                   // Color is empty: 'none' or 'transparent'. Return as is
                   return color;
               }

               // Change black to 'currentColor'
               if (compareColors(color, blackColor)) {
                   return 'currentColor';
               }

               // Remove shapes with white color
               if (compareColors(color, whiteColor)) {
                   return 'remove';
               }

               throw new Error(`Unexpected color "${colorStr}" in attribute ${attr}`);
           },
       });

       // Optimise
       runSVGO(svg);

       // Update paths for compatibility with old software
       deOptimisePaths(svg);

       // SVG instance is detached from icon set, so changes to
       // icon are not stored in icon set automatically.

       // Update icon in icon set
       iconSet.fromSVG(name, svg);
   });

   // Save icon set
   const iconSetContent = iconSet.export();
   await fs.writeFile(
       target,
       JSON.stringify(iconSetContent, null, '\t'),
       'utf8'
   );
})();

Released under the Apache 2.0 License.