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.
import { 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.
import { 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'
);
})();