Bundling custom icons for SVG Framework

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

These examples show how to create icon bundles for SVG framework from custom icon sets. These are very basic examples, for more advanced stuff look in code examples list.

Instructions

This example assumes you have converted custom icon set to IconifyJSON format. If not, see similar example that uses Iconify Tools.

Installation:

# Node.js
npm install --save-dev @iconify/utils
# PHP
composer require iconify/json-tools

PHP version assumes you are using Composer to manage dependencies.

Usage:

  • Change variable target to correct location of bundle.
  • Change variable source to correct location of JSON files.
  • Change list of icons in variable icons to icons you want to bundle.
  • Run script.

Preloading icons

Example below create JavaScript files with icon data loaded with Iconify.addCollection or assigned to IconifyPreload variable, whatever is available. This means bundle can be imported before Iconify SVG framework or after it.

Node.js
/**
* This is a simple example for creating icon bundles for Iconify SVG Framework.
*
* It bundles only icons from Iconify icon sets.
* For bundling custom icons see other code examples in documentation.
*/

import { readFileSync, writeFileSync, mkdirSync } from 'fs';
import { dirname } from 'path';

// Installation: npm install --save-dev @iconify/utils @iconify/json
import { getIcons, stringToIcon, minifyIconSet } from '@iconify/utils';

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

// JSON files location. Filename inside directory must match prefix. For example, for "line-md" prefix icons should be stored in "line-md.json"
const source = 'json';

// Icons to bundle, array
const iconsToBundle = [
   'line-md:home-twotone-alt',
   'line-md:github',
   'line-md:image-twotone',
   'octicon:book-24',
   'octicon:code-square-24',
];

// Organize icons by prefix
const sortedIcons = organizeIconsList(iconsToBundle);

// Load icons data
let output = '';

Object.keys(sortedIcons).forEach((prefix) => {
   const iconsList = sortedIcons[prefix];

   // Load icon set
   const filename = `${source}/${prefix}.json`;
   const iconSet = JSON.parse(readFileSync(filename, 'utf8'));

   // Get data for all icons as string
   const data = getIcons(iconSet, iconsList, true);
   if (!data) {
       throw new Error(`Could not get icons for "${prefix}" icon set.`);
   }
   if (data.not_found?.length) {
       throw new Error(
           `Could not find icons in "${prefix}" icon set: ${data.not_found.join(
               ', '
           )}
`

       );
   }
   minifyIconSet(data);

   output += 'add(' + JSON.stringify(data) + ');\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`
;

// Save to file
const dir = dirname(target);
try {
   mkdirSync(dir, {
       recursive: true,
   });
} catch (err) {
   //
}
writeFileSync(target, output, 'utf8');

console.log(`Saved ${target} (${output.length} bytes)`);

/**
* Sort icon names by prefix
*/

function organizeIconsList(icons: string[]): Record<string, string[]> {
   const sorted: Record<string, string[]> = Object.create(null);
   icons.forEach((icon) => {
       const item = stringToIcon(icon);
       if (!item) {
           return;
       }

       const prefix = item.prefix;
       const prefixList = sorted[prefix] ? sorted[prefix] : (sorted[prefix] = []);

       const name = item.name;
       if (prefixList.indexOf(name) === -1) {
           prefixList.push(name);
       }
   });

   return sorted;
}
PHP
<?php
/**
* This is a simple example for creating icon bundles for Iconify SVG Framework.
*
* It bundles only icons from Iconify JSON files.
* For more advanced examples see other code examples in documentation.
*
* See Iconify Tools documentation on how to convert custom icons to Iconify JSON format:
* https://docs.iconify.design/tools/node/import.html
*/


require './vendor/autoload.php';

// Installation: composer require iconify/json-tools
use Iconify\JSONTools\Collection;

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

// JSON files location. Filename inside directory must match prefix. For example, for "line-md" prefix icons should be stored in "line-md.json"
$source = 'json';

// Icons to bundle, array
$icons = [
   'line-md:home-twotone-alt',
   'line-md:github',
   'line-md:image-twotone',
   'octicon:book-24',
   'octicon:code-square-24',
];

// Organize icons by prefix
$icons = organizeIconsList($icons);

// Load icons data
$output = '';
foreach ($icons as $prefix => $iconsList) {
   // Load icon set
   $collection = new Collection($prefix);
   if (!$collection->loadFromFile($source . '/' . $prefix . '.json')) {
       throw new Error('Cannot find file "' . $prefix . '.json"');
   }

   // Make sure all icons exist
   foreach ($iconsList as $name) {
       if (!$collection->iconExists($name)) {
           // Uncomment next line to throw error if an icon does not exist
           // throw new Error('Could not find icon: "' . $prefix . ':' . $name . '"');
           echo 'Could not find icon: "', $prefix, ':', $name, "\"\n";
       }
   }

   // Get data for all icons as string
   $output .= $collection->scriptify([
       'icons' => $iconsList,
       'callback' => 'add',
       'optimize' => true,
   ]);
}

// 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";

// Save to file
file_put_contents($target, $output);

echo 'Saved ', $target, ' (', strlen($output), " bytes)\n";

/**
* Organize icons list by prefix
*
* Result is an object, where key is prefix, value is array of icon names
*/

function organizeIconsList($icons)
{
   $results = [];

   foreach ($icons as $str) {
       // Split icon to prefix and name
       $icon = stringToIcon($str);
       if ($icon === null || $icon['provider'] !== '') {
           // Invalid name or icon name has provider.
           // All icons in this example are from Iconify, so providers are not supported.
           throw new Error('Invalid icon name: ' . $str);
       }

       $prefix = $icon['prefix'];
       $name = $icon['name'];

       // Add icon to results
       if (!isset($results[$prefix])) {
           $results[$prefix] = [$name];
           continue;
       }
       if (!in_array($name, $results[$prefix])) {
           $results[$prefix][] = $name;
       }
   }

   return $results;
}

/**
* Convert icon name from string to object.
*
* Object properties:
* - provider (ignored in this example)
* - prefix
* - name
*
* This function was converted to PHP from @iconify/utils/src/icon/name.ts
* See https://github.com/iconify/iconify/blob/master/packages/utils/src/icon/name.ts
*/

function stringToIcon($value)
{
   $provider = '';
   $colonSeparated = explode(':', $value);

   // Check for provider with correct '@' at start
   if (substr($value, 0, 1) === '@') {
       // First part is provider
       if (count($colonSeparated) < 2 || count($colonSeparated) > 3) {
           // "@provider:prefix:name" or "@provider:prefix-name"
           return null;
       }
       $provider = substr(array_shift($colonSeparated), 1);
   }

   // Check split by colon: "prefix:name", "provider:prefix:name"
   if (count($colonSeparated) > 3 || !count($colonSeparated)) {
       return null;
   }
   if (count($colonSeparated) > 1) {
       // "prefix:name"
       $name = array_pop($colonSeparated);
       $prefix = array_pop($colonSeparated);
       return [
           // Allow provider without '@': "provider:prefix:name"
           'provider' => count($colonSeparated) > 0 ? $colonSeparated[0] : $provider,
           'prefix' => $prefix,
           'name' => $name,
       ];
   }

   // Attempt to split by dash: "prefix-name"
   $dashSeparated = explode('-', $colonSeparated[0]);
   if (count($dashSeparated) > 1) {
       return [
           'provider' => $provider,
           'prefix' => array_shift($dashSeparated),
           'name' => implode('-', $dashSeparated),
       ];
   }

   return null;
}

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.

It 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.