Simple icon bundle 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 icon sets available in Iconify. These are very basic examples, for more advanced stuff look in code examples list.

Instructions

Installation:

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

PHP version assumes you are using Composer to manage dependencies.

Usage:

  • Change variable target to correct location of bundle.
  • 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';

// Icons to bundle, array
const iconsToBundle = [
   'mdi:home',
   'mdi:account',
   'mdi:login',
   'mdi:logout',
   'openmoji:birthday-cake',
   'openmoji:clinking-glasses',
];

// 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 = require.resolve(`@iconify/json/json/${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 icon sets.
* For bundling custom icons see other code examples in documentation.
*/


require './vendor/autoload.php';

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

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

// Icons to bundle, array
$icons = [
   'mdi:home',
   'mdi:account',
   'mdi:login',
   'mdi:logout',
   'openmoji:birthday-cake',
   'openmoji:clinking-glasses',
];

// 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->loadIconifyCollection($prefix)) {
       throw new Error(
           'Icons with prefix "' . $prefix . '" do not exist in Iconify. Update iconify/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.