Bundling custom icons for SVG Framework

This article is a part of Iconify icon bundles code examples.

This example show how to create icon bundles for SVG framework from SVG files. It uses Iconify Tools to import SVG, clean them up, optimize and export to bundle.

Instructions

Installation:

npm install --save-dev @iconify/tools

Usage:

  • Change variable target to correct location of bundle.
  • Change variable source to correct location of SVG files.
  • Optionally change prefix to prefix you want to use for imported icons.
  • Run script.
create-bundle.ts
/**
* This is an advanced example for creating icon bundles for Iconify SVG Framework.
* It creates a bundle from all SVG files in a directory.
*
* This example uses Iconify Tools to import and clean up icons.
* For Iconify Tools documentation visit https://docs.iconify.design/tools/tools2/
*/

import { promises as fs } from 'fs';
import { dirname } from 'path';

// Installation: npm install --save-dev @iconify/tools
import {
   importDirectory,
   cleanupSVG,
   parseColors,
   isEmptyColor,
   runSVGO,
} from '@iconify/tools';

// File to save bundle to
const target = 'assets/icons-bundle.js';

// SVG files location
const source = 'svg';

// Prefix to use for custom icons
const prefix = 'custom';

// Import icons
(async function () {
   // Import icons
   const iconSet = await importDirectory(source, {
       prefix,
   });

   // Validate, clean up, fix palette and optimise
   await iconSet.forEach(async (name, type) => {
       if (type !== 'icon') {
           return;
       }

       // Get SVG instance for parsing
       const svg = iconSet.toSVG(name);
       if (!svg) {
           // Invalid icon
           iconSet.remove(name);
           return;
       }

       // Clean up and optimise icons
       try {
           // Clean up icon code
           await cleanupSVG(svg);

           // Assume icon is monotone: replace color with currentColor, add if missing
           // If icon is not monotone, remove this code
           await parseColors(svg, {
               defaultColor: 'currentColor',
               callback: (attr, colorStr, color) => {
                   return !color || isEmptyColor(color) ? colorStr : 'currentColor';
               },
           });

           // Optimise
           await runSVGO(svg);
       } catch (err) {
           // Invalid icon
           console.error(`Error parsing ${name}:`, err);
           iconSet.remove(name);
           return;
       }

       // Update icon from SVG instance
       iconSet.fromSVG(name, svg);
   });
   console.log(`Imported ${iconSet.count()} icons`);

   // Export to JSON
   const json = iconSet.export();

   // Export to bundle
   let output = 'add(' + JSON.stringify(json) + ');\n';

   // Wrap in custom code that checks for Iconify.addCollection and IconifyPreload
   output = `(function() {
    function add(data) {
        try {
            if (typeof self.Iconify === 'object' && self.Iconify.addCollection) {
                self.Iconify.addCollection(data);
                return;
            }
            if (typeof self.IconifyPreload === 'undefined') {
                self.IconifyPreload = [];
            }
            self.IconifyPreload.push(data);
        } catch (err) {
        }
    }
    ${output}
})();\n`
;

   // Create directory for output if missing
   const dir = dirname(target);
   try {
       await fs.mkdir(dir, {
           recursive: true,
       });
   } catch (err) {
       //
   }

   // Save to file
   await fs.writeFile(target, output, 'utf8');

   console.log(`Saved ${target} (${output.length} bytes)`);
})().catch((err) => {
   console.error(err);
});

Code above is written with TypeScript. If you want simple JavaScript file, remove types.

Code is asynchronous. It is wrapped in anonymous asynchronous function because top level await, at moment of writing documentation, is not available in all currently used versions of Node.

Usage in HTML

Bundle generated by script above can be included before SVG framework:

<html>
   <head>
       <script src="/assets/icons-bundle.js"></script>
       <script src="https://code.iconify.design/3/3.0.1/iconify.min.js"></script>
   </head>
   <body>
       <!-- content here -->
   </body>
</html>
Loading bundle before SVG framework in head section.
<html>
   <body>
       <!-- content here -->
       <script src="/assets/icons-bundle.js"></script>
       <script src="https://code.iconify.design/3/3.0.1/iconify.min.js"></script>
   </body>
</html>
Loading bundle before SVG framework in footer.

Bundle generated by script above can also be included after SVG framework:

<html>
   <head>
       <script src="https://code.iconify.design/3/3.0.1/iconify.min.js"></script>
       <script src="/assets/icons-bundle.js"></script>
   </head>
   <body>
       <!-- content here -->
   </body>
</html>
Loading bundle after SVG framework in head section.