Converting SVG set to Iconify JSON

This example shows how to convert directory full of SVG files to Iconify JSON format.

This documentation is for version 1 of Iconify Tools. It was developed during early Iconify development, before Iconify development switched to TypeScript, so it is not fully compatible with TypeScript.

Documentation for updated Iconify Tools is available here.

This example uses Iconify Tools 1. Modern version that uses Iconify Tools 2 is available here.

As a source, lets take Material Design icons from Templarian/MaterialDesign-SVG repository that is also available as @mdi/svg NPM package.

Initialize project by running these commands:

npm init
npm install @iconify/tools @mdi/svg --save

Then create file convert-mdi.js and put this content:

convert-mdi.js
'use strict';

const path = require('path');
const util = require('util');
const tools = require('@iconify/tools');

/*
   Locate directory where SVG files are

   Icons are located  in directory "svg" in @mdi/svg package

   require.resolve locates package.json
   path.dirname removes package.json from result, returning only directory
   + '/svg' adds 'svg' directory to result
*/

let source = path.dirname(require.resolve('@mdi/svg/package.json')) + '/svg';

// Target file name
let target = __dirname + '/mdi.json';

// Variable to store collection
let collection;

// Options for SVGO optimization
let SVGOOptions = {
   convertShapeToPath: true,
   mergePaths: false,
};

/**
* Import directory
*/

tools
   .ImportDir(source, {
       prefix: 'mdi',
   })
   .then((result) => {
       // Copy reference so it can be used in chain of promises
       // collection is instance of tools.Collection class
       collection = result;

       console.log('Imported', collection.length(), 'icons.');

       // Optimize SVG files
       //
       // collection.promiseEach() iterates all icons in collection and runs
       // promise for each icon, one at a time.
       return collection.promiseEach(
           (svg, key) =>
               new Promise((fulfill, reject) => {
                   tools
                       .SVGO(svg, SVGOOptions)
                       .then((res) => {
                           fulfill(res);
                       })
                       .catch((err) => {
                           reject('Error optimizing icon ' + key + '\n' + util.format(err));
                       });
               }),
           true
       );
   })
   .then(() => {
       // Clean up tags
       return collection.promiseEach(
           (svg, key) =>
               new Promise((fulfill, reject) => {
                   tools
                       .Tags(svg)
                       .then((res) => {
                           fulfill(res);
                       })
                       .catch((err) => {
                           reject(
                               'Error checking tags in icon ' + key + '\n' + util.format(err)
                           );
                       });
               }),
           true
       );
   })
   .then(() => {
       // Move icons origin to 0,0
       // This is not needed for most collections, but its useful to know how to do it
       let promises = [];
       collection.forEach((svg, key) => {
           if (svg.top !== 0 || svg.left !== 0) {
               let body = svg.getBody();
               if (body.indexOf('<defs') !== -1) {
                   // Do not use this method to move icons with <defs> tags - sometimes results could be wrong
                   return;
               }

               let content = '<svg';
               content += ' width="' + svg.width + '"';
               content += ' height="' + svg.height + '"';
               content += ' viewBox="0 0 ' + svg.width + ' ' + svg.height + '"';
               content += ' xmlns="http://www.w3.org/2000/svg">\n';
               content +=
                   '<g transform="translate(' +
                   (0 - svg.left) +
                   ' ' +
                   (0 - svg.top) +
                   ')">' +
                   body +
                   '</g>';
               content += '</svg>';

               svg.load(content);
               promises.push(
                   new Promise((fulfill, reject) => {
                       // Use SVGO to optimize icon. It will get apply transformation to shapes
                       tools
                           .SVGO(svg, SVGOOptions)
                           .then((res) => {
                               fulfill(res);
                           })
                           .catch((err) => {
                               reject(
                                   'Error changing icon origin for ' +
                                       key +
                                       '\n' +
                                       util.format(err)
                               );
                           });
                   })
               );
           }
       });
       return Promise.all(promises);
   })
   .then(() => {
       // Change color to "currentColor" to all icons
       // Use this only for monotone collections
       let options = {
           default: 'currentColor', // change all colors to "currentColor"
           add: 'currentColor', // add "currentColor" to shapes that are missing color value
       };

       /*
       // For icons that have palette use this instead:
       let options = {
           add: 'currentColor',
       };
       */


       return collection.promiseEach(
           (svg) => tools.ChangePalette(svg, options),
           true
       );
   })
   .then(() => {
       // Export JSON collection
       console.log('Exporting collection to', target);
       return tools.ExportJSON(collection, target, {
           optimize: true,
       });
   })
   .catch((err) => {
       console.error(err);
   });

Then run:

node convert-mdi

Prepared project is available in Iconify Tools GitHub repository.

How does it work?

There are comments in code above that explain what is going on.

Process is simple:

  1. tools.ImportDir() imports all icons from directory "svg" of @mdi/svg package.
  2. tools.SVGO() is used to optimize icons.
  3. tools.Tags() is used to validate and clean up icons.
  4. Then icons' origin is moved to 0,0 and tools.SVO() is used again to optimize icons.
  5. tools.ChangePalette() is used to add "currentColor" to shapes that are missing colors and change existing color to "currentColor" (colored icons use different sets of options, see comments in code).
  6. tools.ExportJSON() is used to export collection to JSON file.

Steps 2, 3 and 4 are actually useless for this example because MDI icons are all well coded, but they are included in example because some SVG sets do require them.

Also see how to import Font Awesome Pro. It is a bit more complex because it imports multiple icon sets at once, but it is also cleaner because it does not include unnecessary steps.

Handling errors when processing icons

In code in this tutorial, if script encounters error, it will throw exception.

But what if you want bad file to be simply ignored? Then you can change stopOnError parameter to promiseEeach() to false or replace reject() with fulfill() while also removing bad icon.

For example, this is code that optimizes icons:

return collection.promiseEach(
   (svg, key) =>
       new Promise((fulfill, reject) => {
           tools
               .SVGO(svg, SVGOOptions)
               .then((res) => {
                   fulfill(res);
               })
               .catch((err) => {
                   reject('Error optimizing icon ' + key + '\n' + util.format(err));
               });
       }),
   true
);

And this is same code that instead of throwing error simply removes bad icon:

return collection.promiseEach(
   (svg, key) =>
       new Promise((fulfill, reject) => {
           tools
               .SVGO(svg, SVGOOptions)
               .then((res) => {
                   fulfill(res);
               })
               .catch((err) => {
                   console.error('Error optimizing icon ' + key, err);
                   collection.remove(key);
                   fulfill(null);
               });
       }),
   false
);